testaro 59.3.0 → 60.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/package.json +5 -2
- package/run.js +57 -14
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ One software product that performs some such functions is [Testilo](https://www.
|
|
|
42
42
|
|
|
43
43
|
Testaro uses:
|
|
44
44
|
- [Playwright](https://playwright.dev/) to launch browsers, perform user actions in them, and perform tests
|
|
45
|
+
- [playwright-extra](https://www.npmjs.com/package/playwright-extra) and [puppeteer-extra-plugin-stealth](https://www.npmjs.com/package/puppeteer-extra-plugin-stealth) to make a Playwright-controlled browser more indistinguishable from a human-operated browser and thus make their requests more likely to succeed
|
|
45
46
|
- [playwright-dompath](https://www.npmjs.com/package/playwright-dompath) to retrieve XPaths of elements
|
|
46
47
|
- [pixelmatch](https://www.npmjs.com/package/pixelmatch) to measure motion
|
|
47
48
|
|
|
@@ -91,7 +92,7 @@ The main directories containing code files are:
|
|
|
91
92
|
|
|
92
93
|
## System requirements
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
The latest long-term-support version of [Node.js](https://nodejs.org/en/).
|
|
95
96
|
|
|
96
97
|
## Installation
|
|
97
98
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testaro",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "60.0.0",
|
|
4
4
|
"description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -41,7 +41,10 @@
|
|
|
41
41
|
"dotenv": "*",
|
|
42
42
|
"pixelmatch": "*",
|
|
43
43
|
"playwright": "*",
|
|
44
|
-
"playwright-dompath": "*"
|
|
44
|
+
"playwright-dompath": "*",
|
|
45
|
+
"playwright-extra": "*",
|
|
46
|
+
"playwright-extra-plugin-stealth": "*",
|
|
47
|
+
"puppeteer-extra-plugin-stealth": "*"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"eslint": "*"
|
package/run.js
CHANGED
|
@@ -36,6 +36,13 @@ const fs = require('fs/promises');
|
|
|
36
36
|
require('dotenv').config({quiet: true});
|
|
37
37
|
// Module to validate jobs.
|
|
38
38
|
const {isBrowserID, isDeviceID, isURL, isValidJob, tools} = require('./procs/job');
|
|
39
|
+
// Module to evade automation detection.
|
|
40
|
+
const {chromium, webkit, firefox} = require('playwright-extra');
|
|
41
|
+
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
|
42
|
+
chromium.use(StealthPlugin());
|
|
43
|
+
webkit.use(StealthPlugin());
|
|
44
|
+
firefox.use(StealthPlugin());
|
|
45
|
+
const playwrightBrowsers = {chromium, webkit, firefox};
|
|
39
46
|
// Module to standardize report formats.
|
|
40
47
|
const {standardize} = require('./procs/standardize');
|
|
41
48
|
// Module to identify element bounding boxes.
|
|
@@ -136,6 +143,15 @@ const getNonce = async response => {
|
|
|
136
143
|
// Return the nonce, if any.
|
|
137
144
|
return nonce;
|
|
138
145
|
};
|
|
146
|
+
// Normalizes a file URL in case it has the Windows path format.
|
|
147
|
+
const normalizeFile = u => {
|
|
148
|
+
if (!u) return u;
|
|
149
|
+
if (!u.toLowerCase().startsWith('file:')) return u;
|
|
150
|
+
// Ensure forward slashes and three slashes after file:
|
|
151
|
+
let path = u.replace(/^file:\/+/i, '');
|
|
152
|
+
path = path.replace(/\\/g, '/');
|
|
153
|
+
return 'file:///' + path.replace(/^\//, '');
|
|
154
|
+
};
|
|
139
155
|
// Visits a URL and returns the response of the server.
|
|
140
156
|
const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
141
157
|
// If the URL is a file path:
|
|
@@ -154,19 +170,10 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
154
170
|
const httpStatus = response.status();
|
|
155
171
|
// If the response status was normal:
|
|
156
172
|
if ([200, 304].includes(httpStatus) || url.startsWith('file:')) {
|
|
157
|
-
// If the browser was redirected in violation of a strictness requirement:
|
|
158
173
|
const actualURL = page.url();
|
|
159
|
-
// Normalize file:// URLs for comparison (handles Windows path formats)
|
|
160
|
-
const normalizeFile = u => {
|
|
161
|
-
if (!u) return u;
|
|
162
|
-
if (!u.toLowerCase().startsWith('file:')) return u;
|
|
163
|
-
// Ensure forward slashes and three slashes after file:
|
|
164
|
-
let path = u.replace(/^file:\/+/i, '');
|
|
165
|
-
path = path.replace(/\\/g, '/');
|
|
166
|
-
return 'file:///' + path.replace(/^\//, '');
|
|
167
|
-
};
|
|
168
174
|
const actualNorm = actualURL.startsWith('file:') ? normalizeFile(actualURL) : actualURL;
|
|
169
175
|
const urlNorm = url.startsWith('file:') ? normalizeFile(url) : url;
|
|
176
|
+
// If the browser was redirected in violation of a strictness requirement:
|
|
170
177
|
if (report.strict && deSlash(actualNorm) !== deSlash(urlNorm)) {
|
|
171
178
|
// Return an error.
|
|
172
179
|
console.log(`ERROR: Visit to ${url} redirected to ${actualURL}`);
|
|
@@ -186,6 +193,15 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
}
|
|
196
|
+
// Otherwise, if the response status was prohibition:
|
|
197
|
+
else if (httpStatus === 403) {
|
|
198
|
+
// Return this.
|
|
199
|
+
console.log(`ERROR: Visit to ${url} prohibited (status 403)`);
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
error: 'status403'
|
|
203
|
+
};
|
|
204
|
+
}
|
|
189
205
|
// Otherwise, if the response status was rejection of excessive requests:
|
|
190
206
|
else if (httpStatus === 429) {
|
|
191
207
|
// Return this.
|
|
@@ -275,7 +291,7 @@ const launch = exports.launch = async (
|
|
|
275
291
|
// Replace the report target URL with this URL.
|
|
276
292
|
report.target.url = url;
|
|
277
293
|
// Create a browser of the specified or default type.
|
|
278
|
-
const browserType =
|
|
294
|
+
const browserType = playwrightBrowsers[browserID];
|
|
279
295
|
// Close the current browser, if any.
|
|
280
296
|
await browserClose();
|
|
281
297
|
// Define browser options.
|
|
@@ -290,17 +306,44 @@ const launch = exports.launch = async (
|
|
|
290
306
|
},
|
|
291
307
|
headless: ! debug,
|
|
292
308
|
slowMo: waits || 0,
|
|
293
|
-
...(browserID === 'chromium' && {
|
|
309
|
+
...(browserID === 'chromium' && {
|
|
310
|
+
args: ['--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled']
|
|
311
|
+
})
|
|
294
312
|
};
|
|
295
313
|
try {
|
|
296
314
|
// Replace the browser with a new one.
|
|
297
315
|
browser = await browserType.launch(browserOptions);
|
|
298
316
|
// Redefine the context (i.e. browser window).
|
|
299
|
-
|
|
317
|
+
const contextOptions = {
|
|
318
|
+
...device.windowOptions,
|
|
319
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
|
320
|
+
viewport: device.windowOptions.viewport || {width: 1920, height: 1080},
|
|
321
|
+
locale: 'en-US',
|
|
322
|
+
timezoneId: 'America/Los_Angeles',
|
|
323
|
+
extraHTTPHeaders: {
|
|
324
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
|
325
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
326
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
327
|
+
'DNT': '1',
|
|
328
|
+
'Upgrade-Insecure-Requests': '1'
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
browserContext = await browser.newContext(contextOptions);
|
|
300
332
|
// Prevent default timeouts.
|
|
301
333
|
browserContext.setDefaultTimeout(0);
|
|
302
|
-
// When a page (i.e.
|
|
334
|
+
// When a page (i.e. tab) is added to the browser context (i.e. browser window):
|
|
303
335
|
browserContext.on('page', async page => {
|
|
336
|
+
// Mask automation detection
|
|
337
|
+
await page.addInitScript(() => {
|
|
338
|
+
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
|
339
|
+
window.chrome = {runtime: {}};
|
|
340
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
341
|
+
get: () => [1, 2, 3, 4, 5]
|
|
342
|
+
});
|
|
343
|
+
Object.defineProperty(navigator, 'languages', {
|
|
344
|
+
get: () => ['en-US', 'en']
|
|
345
|
+
});
|
|
346
|
+
});
|
|
304
347
|
// Ensure the report has a jobData property.
|
|
305
348
|
report.jobData ??= {};
|
|
306
349
|
const {jobData} = report;
|