@telefonica/acceptance-testing 4.1.0 → 5.0.0-beta2

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/dist/index.js CHANGED
@@ -1,8 +1,424 @@
1
-
2
- 'use strict'
3
-
4
- if (process.env.NODE_ENV === 'production') {
5
- module.exports = require('./acceptance-testing.cjs.production.min.js')
6
- } else {
7
- module.exports = require('./acceptance-testing.cjs.development.js')
8
- }
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.waitForElementToBeRemoved = exports.wait = exports.prepareFile = exports.within = exports.screen = exports.getScreen = exports.openPage = exports.createApiEndpointMock = exports.interceptRequest = exports.getPageApi = exports.serverPort = exports.serverHostName = exports.getGlobalPage = exports.getGlobalBrowser = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const pptr_testing_library_1 = require("pptr-testing-library");
10
+ const jest_image_snapshot_1 = require("jest-image-snapshot");
11
+ const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
12
+ const child_process_1 = require("child_process");
13
+ const coverage_1 = require("./coverage");
14
+ const utils_1 = require("./utils");
15
+ const config_1 = require("./config");
16
+ const wsl_1 = require("./wsl");
17
+ const constants_1 = require("./constants");
18
+ const { isCi, server, coveragePath, coverageUrls, collectCoverage } = (0, config_1.getConfig)();
19
+ (0, utils_1.debug)('Config:', (0, config_1.getConfig)());
20
+ const getGlobalBrowser = () => global.browser;
21
+ exports.getGlobalBrowser = getGlobalBrowser;
22
+ const getGlobalPage = () => global.page;
23
+ exports.getGlobalPage = getGlobalPage;
24
+ const isUsingDockerizedChromium = isCi || new URL((0, exports.getGlobalBrowser)().wsEndpoint()).port === '9223';
25
+ exports.serverHostName = (() => {
26
+ if (isCi) {
27
+ return 'localhost';
28
+ }
29
+ if (isUsingDockerizedChromium) {
30
+ if ((0, wsl_1.isWsl)()) {
31
+ if ((0, wsl_1.isWsl2)()) {
32
+ return (0, wsl_1.getWslHostIp)();
33
+ }
34
+ throw new Error('WSL 1 is not supported. Please, use WSL 2.');
35
+ }
36
+ if (process.platform === 'win32') {
37
+ throw new Error('Windows is not supported. Please, use WSL 2.');
38
+ }
39
+ return process.platform === 'linux' ? constants_1.LINUX_DOCKER_HOST_IP : 'host.docker.internal';
40
+ }
41
+ return 'localhost';
42
+ })();
43
+ exports.serverPort = server.port;
44
+ const toMatchImageSnapshot = (0, jest_image_snapshot_1.configureToMatchImageSnapshot)({
45
+ failureThreshold: 0,
46
+ failureThresholdType: 'percent',
47
+ customSnapshotIdentifier: ({ defaultIdentifier }) => defaultIdentifier,
48
+ });
49
+ let calledToMatchImageSnapshotOutsideDocker = false;
50
+ const localToMatchImageSnapshot = () => {
51
+ calledToMatchImageSnapshotOutsideDocker = true;
52
+ // let the expectation pass, then fail in afterEach. This way we allow developers to debug screenshot tests in local
53
+ // but don't allow them to save screenshots taken outside the dockerized chromium
54
+ return {
55
+ message: () => '',
56
+ pass: true,
57
+ };
58
+ };
59
+ expect.extend({
60
+ toMatchImageSnapshot: isUsingDockerizedChromium ? toMatchImageSnapshot : localToMatchImageSnapshot,
61
+ });
62
+ afterEach(() => {
63
+ if (calledToMatchImageSnapshotOutsideDocker) {
64
+ const error = new Error(`Calling .toMatchImageSnapshot() is not allowed outside dockerized browser. Please, run your screenshot test in headless mode.`);
65
+ error.stack = (error.stack || '').split('\n')[0];
66
+ throw error;
67
+ }
68
+ });
69
+ const waitForPaintEnd = async (element, { fullPage = true, captureBeyondViewport } = {}) => {
70
+ const MAX_WAIT = 15000;
71
+ const STEP_TIME = 250;
72
+ const t0 = Date.now();
73
+ let buf1 = (await element.screenshot(normalizeSreenshotOptions({ fullPage, captureBeyondViewport })));
74
+ await new Promise((r) => setTimeout(r, STEP_TIME));
75
+ let buf2 = (await element.screenshot(normalizeSreenshotOptions({ fullPage, captureBeyondViewport })));
76
+ // buffers are different if compare != 0
77
+ while (buf1.compare(buf2)) {
78
+ if (Date.now() - t0 > MAX_WAIT) {
79
+ throw Error('Paint end timeout');
80
+ }
81
+ buf1 = buf2;
82
+ await new Promise((r) => setTimeout(r, STEP_TIME));
83
+ buf2 = (await element.screenshot(normalizeSreenshotOptions({ fullPage, captureBeyondViewport })));
84
+ }
85
+ };
86
+ const normalizeSreenshotOptions = ({ captureBeyondViewport = false, ...options } = {}) => {
87
+ // Puppeter default for captureBeyondViewport is true, but we think false is a better default.
88
+ // When this is true, the fixed elements (like fixed footers) are relative to the original page
89
+ // viewport, not to the full page, so those elements look weird in fullPage screenshots.
90
+ return { ...options, captureBeyondViewport };
91
+ };
92
+ // Puppeteer already calls scrollIntoViewIfNeeded before clicking an element. But it doesn't work in all situations
93
+ // For example, when there is a fixed footer in the page and the element to click is under it, the browser won't scroll
94
+ // because the element is already in the viewport (the ifNeeded part is important here). By forcing the scroll to the
95
+ // center, we manage to fix these edge cases.
96
+ const scrollIntoView = (el) => el.evaluate((e) => e.scrollIntoView({ block: 'center' }));
97
+ const getPageApi = (page) => {
98
+ const api = Object.create(page);
99
+ api.type = async (elementHandle, text, options) => {
100
+ await scrollIntoView(elementHandle);
101
+ return elementHandle.type(text, options);
102
+ };
103
+ api.click = async (elementHandle, options) => {
104
+ await scrollIntoView(elementHandle);
105
+ return elementHandle.click(options);
106
+ };
107
+ api.select = async (elementHandle, ...values) => {
108
+ await scrollIntoView(elementHandle);
109
+ return elementHandle.select(...values);
110
+ };
111
+ api.screenshot = async (options) => {
112
+ if (!options?.skipNetworkWait) {
113
+ await page.waitForNetworkIdle();
114
+ }
115
+ await waitForPaintEnd(page, options);
116
+ return page.screenshot(normalizeSreenshotOptions(options));
117
+ };
118
+ api.clear = async (elementHandle) => {
119
+ await elementHandle.click({ clickCount: 3 });
120
+ await elementHandle.press('Delete');
121
+ };
122
+ // For some reason, puppeteer browserContext.overridePermissions doesn't work with newer chrome versions.
123
+ // This workaround polyfills the browser geolocation api to return the mocked position
124
+ api.setGeolocation = (position) => page.evaluate((position) => {
125
+ window.navigator.geolocation.getCurrentPosition = (callback) => {
126
+ // @ts-expect-error - puppeteer's setGeoLocation does not expect a timestamp to be passed
127
+ callback({
128
+ coords: position,
129
+ });
130
+ };
131
+ }, position);
132
+ return api;
133
+ };
134
+ exports.getPageApi = getPageApi;
135
+ let needsRequestInterception = false;
136
+ let requestHandlers = [];
137
+ const requestInterceptor = async (req) => {
138
+ const { handler } = requestHandlers.find(({ matcher }) => matcher(req)) ?? { handler: null };
139
+ if (!handler) {
140
+ req.continue();
141
+ return;
142
+ }
143
+ const response = await handler(req);
144
+ req.respond(response);
145
+ };
146
+ const interceptRequest = (matcher) => {
147
+ needsRequestInterception = true;
148
+ const spy = jest.fn();
149
+ requestHandlers.push({ matcher, handler: spy });
150
+ return spy;
151
+ };
152
+ exports.interceptRequest = interceptRequest;
153
+ const createApiEndpointMock = ({ origin = '*' } = {}) => {
154
+ const originRegExp = (0, glob_to_regexp_1.default)(origin);
155
+ (0, exports.interceptRequest)((req) => {
156
+ const { origin } = new URL(req.url());
157
+ return req.method() === 'OPTIONS' && !!origin.match(originRegExp);
158
+ }).mockImplementation(() => ({
159
+ status: 204,
160
+ headers: {
161
+ 'Access-Control-Allow-Origin': '*',
162
+ 'Access-Control-Allow-Methods': 'POST,PATCH,PUT,GET,OPTIONS,DELETE',
163
+ 'Access-Control-Allow-Headers': '*',
164
+ },
165
+ }));
166
+ return {
167
+ spyOn(spiedPath, method = 'GET') {
168
+ const matcher = (req) => {
169
+ const { origin, pathname, search } = new URL(req.url());
170
+ const pathWithParams = pathname + search;
171
+ return (req.method() === method &&
172
+ !!origin.match(originRegExp) &&
173
+ (0, utils_1.matchPath)(spiedPath, pathWithParams));
174
+ };
175
+ const spy = jest.fn();
176
+ (0, exports.interceptRequest)(matcher).mockImplementation(async (req) => {
177
+ const spyResult = await spy(req);
178
+ const status = spyResult.status ?? 200;
179
+ const resBody = spyResult.body || spyResult;
180
+ return {
181
+ status,
182
+ headers: {
183
+ 'Access-Control-Allow-Origin': '*',
184
+ },
185
+ contentType: 'application/json',
186
+ body: JSON.stringify(resBody),
187
+ };
188
+ });
189
+ return spy;
190
+ },
191
+ };
192
+ };
193
+ exports.createApiEndpointMock = createApiEndpointMock;
194
+ const openPage = async ({ userAgent, isDarkMode, viewport, cookies, ...urlConfig }) => {
195
+ const url = (() => {
196
+ if (urlConfig.url !== undefined) {
197
+ return urlConfig.url;
198
+ }
199
+ const { path = '/', port = exports.serverPort, protocol = 'http', hostname = exports.serverHostName } = urlConfig;
200
+ if (!port) {
201
+ // Error.captureStackTrace(error, openPage);
202
+ throw new Error('You must specify a port. You can specify it when calling openPage() or by configuring a dev and ci server in the acceptanceTests config in your package.json');
203
+ }
204
+ return `${protocol}://${hostname}:${port}${path}`;
205
+ })();
206
+ (0, utils_1.debug)('Opening page:', url);
207
+ const currentUserAgent = userAgent || (await (0, exports.getGlobalBrowser)().userAgent());
208
+ const page = (0, exports.getGlobalPage)();
209
+ await page.bringToFront();
210
+ if (viewport) {
211
+ await page.setViewport(viewport);
212
+ }
213
+ if (cookies) {
214
+ await page.setCookie(...cookies);
215
+ }
216
+ await page.setUserAgent(`${currentUserAgent} acceptance-test`);
217
+ await page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: isDarkMode ? 'dark' : 'light' }]);
218
+ // A set of styles to make screenshot tests more reliable.
219
+ await page.evaluateOnNewDocument((viewport) => {
220
+ const overriddenSafeAreaInsets = !viewport
221
+ ? []
222
+ : Object.keys(viewport?.safeAreaInset ?? {}).map((key) => {
223
+ const position = key;
224
+ return `--acceptance-test-override-safe-area-inset-${key}: ${viewport?.safeAreaInset?.[position]};`;
225
+ });
226
+ const style = document.createElement('style');
227
+ style.innerHTML = `
228
+ *, *:after, *:before {
229
+ transition-delay: 0s !important;
230
+ transition-duration: 0s !important;
231
+ animation-delay: -0.0001s !important;
232
+ animation-duration: 0s !important;
233
+ animation-play-state: paused !important;
234
+ caret-color: transparent !important;
235
+ font-variant-ligatures: none !important;
236
+ }
237
+ *::-webkit-scrollbar {
238
+ display: none !important;
239
+ width: 0 !important;
240
+ height: 0 !important;
241
+ }
242
+ :root {
243
+ ${overriddenSafeAreaInsets.join('\n')}
244
+ }
245
+ `;
246
+ window.addEventListener('DOMContentLoaded', () => {
247
+ document.head.appendChild(style);
248
+ });
249
+ }, viewport);
250
+ if (needsRequestInterception) {
251
+ await page.setRequestInterception(true);
252
+ page.on('request', requestInterceptor);
253
+ }
254
+ try {
255
+ await page.goto(url);
256
+ }
257
+ catch (e) {
258
+ if (e.message.includes('net::ERR_CONNECTION_REFUSED')) {
259
+ const connectionError = new Error(`Could not connect to ${url}. Is the server running?`);
260
+ Error.captureStackTrace(connectionError, exports.openPage);
261
+ throw connectionError;
262
+ }
263
+ else {
264
+ throw e;
265
+ }
266
+ }
267
+ await page.waitForFunction('document.fonts.status === "loaded"');
268
+ return (0, exports.getPageApi)(page);
269
+ };
270
+ exports.openPage = openPage;
271
+ const buildQueryMethods = ({ page, element } = {}) => {
272
+ const boundQueries = {};
273
+ for (const [queryName, queryFn] of Object.entries(pptr_testing_library_1.queries)) {
274
+ boundQueries[queryName] = async (...args) => {
275
+ const doc = await (0, pptr_testing_library_1.getDocument)(page ?? (0, exports.getGlobalPage)());
276
+ const body = await doc.$('body');
277
+ const queryArgs = [...args];
278
+ if (queryName.startsWith('findBy')) {
279
+ if (queryArgs.length === 1) {
280
+ queryArgs.push(undefined);
281
+ }
282
+ queryArgs.push({ timeout: 10000 });
283
+ }
284
+ const elementHandle = await queryFn(element ?? body, ...queryArgs);
285
+ const newElementHandle = Object.create(elementHandle);
286
+ newElementHandle.screenshot = async (options) => {
287
+ if (!options?.skipNetworkWait) {
288
+ await (page ?? (0, exports.getGlobalPage)()).waitForNetworkIdle();
289
+ }
290
+ await waitForPaintEnd(elementHandle, { ...options, fullPage: false });
291
+ return elementHandle.screenshot(normalizeSreenshotOptions(options));
292
+ };
293
+ newElementHandle.click = async (options) => {
294
+ await scrollIntoView(elementHandle);
295
+ return elementHandle.click(options);
296
+ };
297
+ newElementHandle.type = async (text, options) => {
298
+ await scrollIntoView(elementHandle);
299
+ return elementHandle.type(text, options);
300
+ };
301
+ newElementHandle.select = async (...values) => {
302
+ await scrollIntoView(elementHandle);
303
+ return elementHandle.select(...values);
304
+ };
305
+ return newElementHandle;
306
+ };
307
+ }
308
+ return boundQueries;
309
+ };
310
+ const getScreen = (page) => buildQueryMethods({ page });
311
+ exports.getScreen = getScreen;
312
+ exports.screen = buildQueryMethods();
313
+ const within = (element) => buildQueryMethods({ element });
314
+ exports.within = within;
315
+ beforeEach(async () => {
316
+ await (0, exports.getGlobalPage)().setRequestInterception(false);
317
+ // by resetting the page we clean up all the evaluateOnNewDocument calls, which are persistent between documents
318
+ await global.jestPuppeteer.resetPage();
319
+ });
320
+ afterEach(async () => {
321
+ if (collectCoverage) {
322
+ await (0, coverage_1.collectFrontendCoverage)({ coveragePath });
323
+ }
324
+ try {
325
+ const page = (0, exports.getGlobalPage)();
326
+ requestHandlers = [];
327
+ needsRequestInterception = false;
328
+ page.off('request', requestInterceptor);
329
+ // clear tab, this way we clear the DOM and stop js execution or pending requests
330
+ await page.goto('about:blank');
331
+ }
332
+ catch (e) {
333
+ // ignore, at this point page might be destroyed
334
+ }
335
+ });
336
+ afterAll(async () => {
337
+ if (collectCoverage) {
338
+ await (0, coverage_1.collectBackendCoverage)({ coveragePath, coverageUrls });
339
+ }
340
+ });
341
+ /**
342
+ * Returns a new path to the file that can be used by chromium in acceptance tests
343
+ *
344
+ * To be able to use `element.uploadFile()` in a dockerized chromium, the file must exist in the
345
+ * host and the docker, and both sides must use the same path.
346
+ *
347
+ * To workaround this bug or limitation, this function prepares the file by copying it to /tmp in
348
+ * the host and the container.
349
+ */
350
+ const prepareFile = (filepath) => {
351
+ const isLocal = !isCi;
352
+ const isHeadless = !!process.env.HEADLESS;
353
+ const usesDocker = isLocal && isHeadless;
354
+ const dockerComposeFile = path_1.default.join(__dirname, '..', 'docker-compose.yaml');
355
+ if (usesDocker) {
356
+ const containerId = (0, child_process_1.execSync)(`docker compose -f ${dockerComposeFile} ps -q`).toString().trim();
357
+ if (!containerId) {
358
+ throw Error('acceptance-testing container not found');
359
+ }
360
+ (0, child_process_1.execSync)(`docker cp ${filepath} ${containerId}:/tmp`);
361
+ const newPath = path_1.default.join('/tmp', path_1.default.basename(filepath));
362
+ fs_1.default.copyFileSync(filepath, newPath);
363
+ return newPath;
364
+ }
365
+ else {
366
+ return filepath;
367
+ }
368
+ };
369
+ exports.prepareFile = prepareFile;
370
+ /**
371
+ * A convenience method to defer an expectation
372
+ */
373
+ const wait = (expectation, timeout = 10000, interval = 50) => {
374
+ const startTime = Date.now();
375
+ const startStack = new Error().stack;
376
+ return new Promise((resolve, reject) => {
377
+ const rejectOrRerun = (error) => {
378
+ if (Date.now() - startTime >= timeout) {
379
+ if (error instanceof Error) {
380
+ if (error.message === 'Element not removed') {
381
+ error.stack = startStack;
382
+ }
383
+ }
384
+ reject(error);
385
+ return;
386
+ }
387
+ setTimeout(runExpectation, interval);
388
+ };
389
+ const runExpectation = () => {
390
+ try {
391
+ Promise.resolve(expectation())
392
+ .then((r) => resolve(r))
393
+ .catch(rejectOrRerun);
394
+ }
395
+ catch (error) {
396
+ rejectOrRerun(error);
397
+ }
398
+ };
399
+ setTimeout(runExpectation, 0);
400
+ });
401
+ };
402
+ exports.wait = wait;
403
+ const waitForElementToBeRemoved = (element, timeout = 10000, interval = 100) => {
404
+ const startStack = new Error().stack;
405
+ const wait = async () => {
406
+ const t0 = Date.now();
407
+ while (Date.now() - t0 < timeout) {
408
+ // boundingBox returns null when the element is not in the DOM
409
+ const box = await element.boundingBox();
410
+ if (!box) {
411
+ return;
412
+ }
413
+ await new Promise((resolve) => setTimeout(resolve, interval));
414
+ }
415
+ throw new Error('Element not removed');
416
+ };
417
+ return wait().catch((error) => {
418
+ if (error.message === 'Element not removed') {
419
+ error.stack = startStack;
420
+ }
421
+ throw error;
422
+ });
423
+ };
424
+ exports.waitForElementToBeRemoved = waitForElementToBeRemoved;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,89 +1,53 @@
1
- const path = require('path');
2
- const fs = require('fs');
3
- const findRoot = require('find-root');
4
- const fetch = require('node-fetch');
5
- const execSync = require('child_process').execSync;
6
-
7
- const poll = async (url) => {
8
- let tries = 10;
9
- while (tries--) {
10
- try {
11
- return await fetch(url);
12
- } catch (e) {
13
- await new Promise((r) => setTimeout(r, 500));
14
- }
15
- }
16
- throw Error(`Error fetching ${url}`);
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
4
  };
18
-
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("./utils");
7
+ const path_1 = __importDefault(require("path"));
8
+ const child_process_1 = require("child_process");
9
+ const config_1 = require("./config");
19
10
  /**
20
11
  * CI => everything runs inside a docker
21
12
  * Local, headless => connects to a dockerized chromium at port 9223 (see docker-compose.yaml)
22
13
  * Local, with UI => launches a local chromium installed by puppetteer
23
14
  */
24
15
  const getPuppeteerConfig = async () => {
25
- const isCi = process.argv.includes('--ci') || process.env.CI;
26
- const isLocal = !isCi;
27
- const isHeadless = !!process.env.HEADLESS;
28
-
29
- const rootDir = findRoot(process.cwd());
30
- const pkg = JSON.parse(fs.readFileSync(path.join(rootDir, 'package.json'), 'utf-8'));
31
- const projectConfig = pkg.acceptanceTests || {};
32
-
33
- const server = (isCi ? projectConfig.ciServer : projectConfig.devServer) || projectConfig.server || {};
34
-
16
+ const { isCi, isLocal, isHeadless, server } = (0, config_1.getConfig)();
35
17
  const baseConfig = {
36
18
  ignoreHTTPSErrors: true,
37
19
  headless: isHeadless,
38
20
  slowMo: isHeadless ? 0 : 50,
39
21
  };
40
-
41
22
  let connect;
42
-
43
23
  if (isLocal && isHeadless) {
44
24
  const dockerChromiumUrl = 'http://localhost:9223';
45
-
46
25
  try {
47
26
  await fetch(dockerChromiumUrl);
48
- } catch (e) {
49
- const dockerComposeFile = path.join(__dirname, 'docker-compose.yaml');
50
-
27
+ }
28
+ catch (e) {
29
+ const dockerComposeFile = path_1.default.join(__dirname, '/../docker-compose.yaml');
51
30
  process.on('SIGINT', () => {
52
31
  process.exit(1); // this triggers the 'exit' event handler below when the process is killed with Ctrl+C
53
32
  });
54
-
55
33
  process.on('exit', () => {
56
- execSync(`docker compose -f ${dockerComposeFile} stop -t 0`, {stdio: 'ignore'});
34
+ (0, child_process_1.execSync)(`docker compose -f ${dockerComposeFile} stop -t 0`, { stdio: 'ignore' });
57
35
  });
58
-
59
- execSync(`docker compose -f ${dockerComposeFile} up -d`, {stdio: 'inherit'});
36
+ (0, child_process_1.execSync)(`docker compose -f ${dockerComposeFile} up -d`, { stdio: 'inherit' });
60
37
  console.log();
61
-
62
- await poll(dockerChromiumUrl);
38
+ await (0, utils_1.poll)(dockerChromiumUrl);
63
39
  }
64
-
65
- const {webSocketDebuggerUrl} = await fetch(`${dockerChromiumUrl}/json/version`).then((r) => r.json());
66
-
40
+ const { webSocketDebuggerUrl } = await fetch(`${dockerChromiumUrl}/json/version`).then((r) => r.json());
67
41
  connect = {
68
42
  ...baseConfig,
69
43
  browserWSEndpoint: webSocketDebuggerUrl,
70
44
  };
71
45
  }
72
-
73
- const defaultServerConfig = {
74
- command: 'tail -f /dev/null',
75
- host: 'localhost',
76
- protocol: server.path ? 'http' : 'tcp',
77
- debug: isCi,
78
- usedPortAction: isCi ? 'error' : 'ignore', // In dev, if port is already taken, assume server is already running.
79
- launchTimeout: 60000,
80
- };
81
-
82
46
  return {
83
47
  launch: {
84
48
  ...baseConfig,
85
49
  // Launch chromium installed in docker in CI
86
- ...(isCi ? {executablePath: '/usr/bin/chromium'} : {}),
50
+ ...(isCi ? { executablePath: '/usr/bin/chromium' } : {}),
87
51
  env: {
88
52
  ...process.env,
89
53
  TZ: 'UTC',
@@ -112,13 +76,8 @@ const getPuppeteerConfig = async () => {
112
76
  },
113
77
  connect,
114
78
  browserContext: 'incognito',
115
- server: server
116
- ? {
117
- ...defaultServerConfig,
118
- ...server,
119
- }
120
- : undefined,
79
+ server,
80
+ exitOnPageError: !process.env.ACCEPTANCE_TESTING_IGNORE_PAGE_ERRORS,
121
81
  };
122
82
  };
123
-
124
83
  module.exports = getPuppeteerConfig();
package/dist/utils.d.ts CHANGED
@@ -1,8 +1,12 @@
1
- /**
2
- * Debug function that logs to the console if the DEBUG_ACCEPTANCE_TESTING environment variable is set
3
- */
4
- export declare const debug: (...args: Array<unknown>) => void;
5
- /**
6
- * Returns true if the current path matches the spied path, including parameters
7
- */
8
- export declare const matchPath: (spiedPath: string, currentPath: string) => boolean;
1
+ /**
2
+ * Debug function that logs to the console if the ACCEPTANCE_TESTING_DEBUG environment variable is set
3
+ */
4
+ export declare const debug: (...args: Array<unknown>) => void;
5
+ /**
6
+ * Returns true if the current path matches the spied path, including parameters
7
+ */
8
+ export declare const matchPath: (spiedPath: string, currentPath: string) => boolean;
9
+ /**
10
+ * Polls a URL until it returns a successful response
11
+ */
12
+ export declare const poll: (url: string) => Promise<Response>;
package/dist/utils.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.poll = exports.matchPath = exports.debug = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const glob_to_regexp_1 = __importDefault(require("glob-to-regexp"));
9
+ const config_1 = require("./config");
10
+ /**
11
+ * Debug function that logs to the console if the ACCEPTANCE_TESTING_DEBUG environment variable is set
12
+ */
13
+ const debug = (...args) => {
14
+ if ((0, config_1.getConfig)().debug) {
15
+ console.debug('[acceptance-testing]', ...args);
16
+ }
17
+ };
18
+ exports.debug = debug;
19
+ /**
20
+ * Returns true if the current path matches the spied path, including parameters
21
+ */
22
+ const matchPath = (spiedPath, currentPath) => {
23
+ const normalizedCurrentPath = path_1.default.normalize(currentPath);
24
+ const pattern = (0, glob_to_regexp_1.default)(spiedPath);
25
+ return pattern.test(normalizedCurrentPath);
26
+ };
27
+ exports.matchPath = matchPath;
28
+ /**
29
+ * Polls a URL until it returns a successful response
30
+ */
31
+ const poll = async (url) => {
32
+ let tries = 10;
33
+ while (tries--) {
34
+ try {
35
+ return await fetch(url);
36
+ }
37
+ catch (e) {
38
+ await new Promise((r) => setTimeout(r, 500));
39
+ }
40
+ }
41
+ throw Error(`Error fetching ${url}`);
42
+ };
43
+ exports.poll = poll;
package/dist/wsl.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** Check if running inside WSL */
2
+ export declare const isWsl: () => boolean;
3
+ /** Assumes running inside WSL */
4
+ export declare const isWsl2: () => boolean;
5
+ /** Assumes running inside WSL */
6
+ export declare const getWslHostIp: () => string;
package/dist/wsl.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getWslHostIp = exports.isWsl2 = exports.isWsl = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const constants_1 = require("./constants");
6
+ /** Check if running inside WSL */
7
+ const isWsl = () => {
8
+ if (process.platform !== 'linux') {
9
+ return false;
10
+ }
11
+ try {
12
+ return (0, child_process_1.execSync)('which wsl.exe').toString().startsWith('/mnt/');
13
+ }
14
+ catch (e) {
15
+ // catched because it throws an error if wsl.exe is not found
16
+ return false;
17
+ }
18
+ };
19
+ exports.isWsl = isWsl;
20
+ /** Assumes running inside WSL */
21
+ const isWsl2 = () => {
22
+ // `wsl.exe -l -v` returns something like:
23
+ //
24
+ // NAME STATE VERSION
25
+ // * Ubuntu-22.04 Running 2
26
+ // docker-desktop-data Running 2
27
+ // docker-desktop Running 2
28
+ // Ubuntu-20.04 Stopped 2
29
+ return ((0, child_process_1.execSync)('wsl.exe -l -v')
30
+ .toString()
31
+ // null-bytes are inserted between chars in this command output (UTF-16 output?)
32
+ .replace(/\0/g, '')
33
+ .split('\n')
34
+ .map((line) => line.trim())
35
+ // matches a line like: "* Ubuntu-22.04 Running 2"
36
+ .some((line) => !!line.match(/\*\s+\S+\s+Running\s+2/)));
37
+ };
38
+ exports.isWsl2 = isWsl2;
39
+ /** Assumes running inside WSL */
40
+ const getWslHostIp = () => {
41
+ const ips = (0, child_process_1.execSync)('wsl.exe hostname -I').toString().trim().split(/\s+/);
42
+ if (ips.includes(constants_1.LINUX_DOCKER_HOST_IP)) {
43
+ // looks like a docker engine installed inside linux
44
+ return constants_1.LINUX_DOCKER_HOST_IP;
45
+ }
46
+ // assuming docker-desktop installed in windows
47
+ return ips[0];
48
+ };
49
+ exports.getWslHostIp = getWslHostIp;
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/jest.puppeteer-config.js');