From 097d2c45ff7c7b050386562878ae957ac90d34cf Mon Sep 17 00:00:00 2001 From: Zui Young Date: Tue, 10 Dec 2024 13:19:26 +0800 Subject: [PATCH] Clickable elements check to error and Playwright dependency browser crash issue (#421) * Resolves clickable checks for iframe * Cleanup console messages * Resolve playwright dependency > 1.46.1 causing browser to crash. --- package-lock.json | 16 +-- package.json | 2 +- src/crawlers/commonCrawlerFunc.ts | 163 ++++++++++++++++++++--- src/mergeAxeResults.ts | 4 +- src/screenshotFunc/htmlScreenshotFunc.ts | 2 +- 5 files changed, 154 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66aa8e03..e297972a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "mime-types": "^2.1.35", "minimatch": "^9.0.3", "pdfjs-dist": "github:veraPDF/pdfjs-dist#v2.14.305-taggedPdf-0.1.11", - "playwright": "^1.49.0", + "playwright": "1.46.1", "prettier": "^3.1.0", "print-message": "^3.0.1", "safe-regex": "^2.1.1", @@ -8558,11 +8558,11 @@ } }, "node_modules/playwright": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", - "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz", + "integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==", "dependencies": { - "playwright-core": "1.49.0" + "playwright-core": "1.46.1" }, "bin": { "playwright": "cli.js" @@ -8575,9 +8575,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", - "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz", + "integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==", "bin": { "playwright-core": "cli.js" }, diff --git a/package.json b/package.json index e90ada6b..9d86cc6c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "mime-types": "^2.1.35", "minimatch": "^9.0.3", "pdfjs-dist": "github:veraPDF/pdfjs-dist#v2.14.305-taggedPdf-0.1.11", - "playwright": "^1.49.0", + "playwright": "1.46.1", "prettier": "^3.1.0", "print-message": "^3.0.1", "safe-regex": "^2.1.1", diff --git a/src/crawlers/commonCrawlerFunc.ts b/src/crawlers/commonCrawlerFunc.ts index baccf5c7..518db77b 100644 --- a/src/crawlers/commonCrawlerFunc.ts +++ b/src/crawlers/commonCrawlerFunc.ts @@ -361,9 +361,8 @@ export const runAxeScript = async ({ check => check.id === 'oobee-grading-text-contents', ); if (gradingCheck) { - gradingCheck.metadata.messages.incomplete = `The text content is potentially difficult to read, with a Flesch-Kincaid Reading Ease score of ${ - gradingReadabilityFlag - }.\nThe target passing score is above 50, indicating content readable by university students and lower grade levels.\nA higher score reflects better readability.`; + gradingCheck.metadata.messages.incomplete = `The text content is potentially difficult to read, with a Flesch-Kincaid Reading Ease score of ${gradingReadabilityFlag + }.\nThe target passing score is above 50, indicating content readable by university students and lower grade levels.\nA higher score reflects better readability.`; } // Fail if readability issues are detected @@ -375,22 +374,22 @@ export const runAxeScript = async ({ .concat( enableWcagAaa ? [ - { - id: 'color-contrast-enhanced', - enabled: true, - tags: ['wcag2aaa', 'wcag146'], - }, - { - id: 'identical-links-same-purpose', - enabled: true, - tags: ['wcag2aaa', 'wcag249'], - }, - { - id: 'meta-refresh-no-exceptions', - enabled: true, - tags: ['wcag2aaa', 'wcag224', 'wcag325'], - }, - ] + { + id: 'color-contrast-enhanced', + enabled: true, + tags: ['wcag2aaa', 'wcag146'], + }, + { + id: 'identical-links-same-purpose', + enabled: true, + tags: ['wcag2aaa', 'wcag249'], + }, + { + id: 'meta-refresh-no-exceptions', + enabled: true, + tags: ['wcag2aaa', 'wcag224', 'wcag325'], + }, + ] : [], ), }); @@ -410,8 +409,129 @@ export const runAxeScript = async ({ const escapedCssSelectors = oobeeAccessibleLabelFlaggedCssSelectors.map(escapeCSSSelector); + function frameCheck(cssSelector: string): { doc: Document; remainingSelector: string } { + let doc = document; // Start with the main document + let frameSelector = ""; // To store the frame part of the selector + + // Extract the 'frame' part of the selector + let frameMatch = cssSelector.match(/(frame[^>]*>)/i); + if (frameMatch) { + frameSelector = frameMatch[1].replace(">", "").trim(); // Clean up the frame part + cssSelector = cssSelector.split(frameMatch[1])[1].trim(); // Remove the frame portion + } + + let targetFrame = null; // Target frame element + + // Locate the frame based on the extracted frameSelector + if (frameSelector.includes("first-of-type")) { + // Select the first frame + targetFrame = document.querySelector("frame:first-of-type"); + } else if (frameSelector.includes("nth-of-type")) { + // Select the nth frame + let nthIndex = frameSelector.match(/nth-of-type\((\d+)\)/); + if (nthIndex) { + let index = parseInt(nthIndex[1]) - 1; // Zero-based index + targetFrame = document.querySelectorAll("frame")[index]; + } + } else if (frameSelector.includes("#")) { + // Frame with a specific ID + let idMatch = frameSelector.match(/#([\w-]+)/); + if (idMatch) { + targetFrame = document.getElementById(idMatch[1]); + } + } else if (frameSelector.includes('[name="')) { + // Frame with a specific name attribute + let nameMatch = frameSelector.match(/name="([\w-]+)"/); + if (nameMatch) { + targetFrame = document.querySelector(`frame[name="${nameMatch[1]}"]`); + } + } else { + // Default to the first frame + targetFrame = document.querySelector("frame"); + } + + // Update the document if the frame was found + if (targetFrame && targetFrame.contentDocument) { + doc = targetFrame.contentDocument; + } else { + console.warn("Frame not found or contentDocument inaccessible."); + } + + return { doc, remainingSelector: cssSelector }; + } + + function iframeCheck(cssSelector: string): { doc: Document; remainingSelector: string } { + let doc = document; // Start with the main document + let iframeSelector = ""; // To store the iframe part of the selector + + // Extract the 'iframe' part of the selector + let iframeMatch = cssSelector.match(/(iframe[^>]*>)/i); + if (iframeMatch) { + iframeSelector = iframeMatch[1].replace(">", "").trim(); // Clean up the iframe part + cssSelector = cssSelector.split(iframeMatch[1])[1].trim(); // Remove the iframe portion + } + + let targetIframe = null; // Target iframe element + + // Locate the iframe based on the extracted iframeSelector + if (iframeSelector.includes("first-of-type")) { + // Select the first iframe + targetIframe = document.querySelector("iframe:first-of-type"); + } else if (iframeSelector.includes("nth-of-type")) { + // Select the nth iframe + let nthIndex = iframeSelector.match(/nth-of-type\((\d+)\)/); + if (nthIndex) { + let index = parseInt(nthIndex[1]) - 1; // Zero-based index + targetIframe = document.querySelectorAll("iframe")[index]; + } + } else if (iframeSelector.includes("#")) { + // Iframe with a specific ID + let idMatch = iframeSelector.match(/#([\w-]+)/); + if (idMatch) { + targetIframe = document.getElementById(idMatch[1]); + } + } else if (iframeSelector.includes('[name="')) { + // Iframe with a specific name attribute + let nameMatch = iframeSelector.match(/name="([\w-]+)"/); + if (nameMatch) { + targetIframe = document.querySelector(`iframe[name="${nameMatch[1]}"]`); + } + } else { + // Default to the first iframe + targetIframe = document.querySelector("iframe"); + } + + // Update the document if the iframe was found + if (targetIframe && targetIframe.contentDocument) { + doc = targetIframe.contentDocument; + } else { + console.warn("Iframe not found or contentDocument inaccessible."); + } + + return { doc, remainingSelector: cssSelector }; + } + function findElementByCssSelector(cssSelector: string): string | null { - let element = document.querySelector(cssSelector); + let doc = document; + + // Check if the selector includes 'frame' and update doc and selector + if (cssSelector.includes("frame")) { + const result = frameCheck(cssSelector); + doc = result.doc; + cssSelector = result.remainingSelector; + } + + // Check for iframe + if (cssSelector.includes("iframe")) { + const result = iframeCheck(cssSelector); + doc = result.doc; + cssSelector = result.remainingSelector; + } + + // Query the element in the document (including inside frames) + let element = doc.querySelector(cssSelector); + + // Handle Shadow DOM if the element is not found if (!element) { const shadowRoots = []; const allElements = document.querySelectorAll('*'); @@ -432,6 +552,7 @@ export const runAxeScript = async ({ } } } + return element ? element.outerHTML : null; } @@ -532,4 +653,4 @@ export const isUrlPdf = (url: string) => { } const parsedUrl = new URL(url); return /\.pdf($|\?|#)/i.test(parsedUrl.pathname) || /\.pdf($|\?|#)/i.test(parsedUrl.href); -}; +}; \ No newline at end of file diff --git a/src/mergeAxeResults.ts b/src/mergeAxeResults.ts index 721389e5..a4ec1911 100644 --- a/src/mergeAxeResults.ts +++ b/src/mergeAxeResults.ts @@ -443,12 +443,12 @@ function writeLargeJsonToFile(obj, filePath) { const writeStream = fs.createWriteStream(filePath, { encoding: 'utf8' }); writeStream.on('error', error => { - console.error('Stream error:', error); + consoleLogger.error('Stream error:', error); reject(error); }); writeStream.on('finish', () => { - console.log('File written successfully:', filePath); + consoleLogger.info('Temporary file written successfully:', filePath); resolve(true); }); diff --git a/src/screenshotFunc/htmlScreenshotFunc.ts b/src/screenshotFunc/htmlScreenshotFunc.ts index ec8c35c8..d802b999 100644 --- a/src/screenshotFunc/htmlScreenshotFunc.ts +++ b/src/screenshotFunc/htmlScreenshotFunc.ts @@ -32,7 +32,7 @@ export const takeScreenshotForHTMLElements = async ( // Check if rule ID is 'oobee-grading-text-contents' and skip screenshot logic if (rule === 'oobee-grading-text-contents') { - console.log('Skipping screenshot for rule oobee-grading-text-contents'); + consoleLogger.info('Skipping screenshot for rule oobee-grading-text-contents'); newViolations.push(violation); // Make sure it gets added continue; }