flexysnap 0.5.2 → 0.5.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/testUtils.js +108 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flexysnap",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Flexible visual snapshot testing for Playwright, built for e-commerce.",
5
5
  "keywords": [
6
6
  "playwright",
package/src/testUtils.js CHANGED
@@ -7,43 +7,92 @@ async function callClosePopups(page) {
7
7
  }
8
8
 
9
9
 
10
- async function gotoWithRetry(page, url) {
10
+ async function attemptNavigation(page, url, timeout) {
11
+ logTimestamp('Loading ' + url);
12
+ let response = null;
13
+ try {
14
+ response = await page.goto(url, {timeout: timeout, waitUntil: "commit"});
15
+ } catch (e) {
16
+ logTimestamp('Error navigating to page');
17
+ throw e
18
+ }
19
+ if (response && response.status() >= 400) {
20
+ logTimestamp(`HTTP error ${response.status()} ${response.statusText()} while loading ${url}`);
21
+ throw new Error('Could not load page.')
22
+ }
23
+ try {
24
+ await page.waitForLoadState('domcontentloaded', {timeout: timeout});
25
+ } catch (e) {
26
+ logTimestamp('Error while waiting for DOMContentLoaded event');
27
+ throw e
28
+ }
29
+ try {
30
+ await page.waitForLoadState('load', {timeout: timeout});
31
+ } catch (e) {
32
+ logTimestamp('Error while waiting for load event');
33
+ throw e
34
+ }
35
+ }
36
+
37
+ const CLOSE_STUCK_TARGET_TIMEOUT_MS = 5000;
38
+ const replacementContexts = [];
39
+
40
+ function closeWithinTimeout(closeable, timeoutMs) {
41
+ let timeoutHandle;
42
+ const timeoutPromise = new Promise(resolve => {
43
+ timeoutHandle = setTimeout(resolve, timeoutMs);
44
+ });
45
+ const closePromise = closeable.close().catch(() => {});
46
+ return Promise.race([closePromise, timeoutPromise]).finally(() => clearTimeout(timeoutHandle));
47
+ }
48
+
49
+ async function openReplacementPage(stuckPage, contextOptions) {
50
+ const stuckContext = stuckPage.context();
51
+ const browser = stuckContext.browser();
52
+
53
+ if (!browser) {
54
+ await closeWithinTimeout(stuckPage, CLOSE_STUCK_TARGET_TIMEOUT_MS);
55
+ return stuckContext.newPage();
56
+ }
57
+
58
+ const viewport = stuckPage.viewportSize();
59
+ await closeWithinTimeout(stuckContext, CLOSE_STUCK_TARGET_TIMEOUT_MS);
60
+
61
+ const replacementContext = await browser.newContext({ viewport, ...contextOptions });
62
+ replacementContexts.push(replacementContext);
63
+ await configureContext(replacementContext);
64
+
65
+ return replacementContext.newPage();
66
+ }
67
+
68
+ async function prepareRetry(stuckPage, waitMs, contextOptions) {
69
+ logTimestamp('Discarding stuck page and opening a new browser context');
70
+ const replacementPage = await openReplacementPage(stuckPage, contextOptions);
71
+ logTimestamp(`Waiting ${waitMs}ms before retrying`);
72
+ await replacementPage.waitForTimeout(waitMs);
73
+ return replacementPage;
74
+ }
75
+
76
+ async function gotoWithRetry(page, url, options = {}) {
77
+ const { baseTimeout = 20000, backoffMs = 3000, contextOptions = {} } = options;
78
+ const maxAttempts = 3;
11
79
  let error = null;
12
- for (let i = 1; i <= 3; ++i) {
80
+ let currentPage = page;
81
+ for (let i = 1; i <= maxAttempts; ++i) {
13
82
  error = null;
14
- let timeout = i * 5000;
15
- logTimestamp('Loading ' + url);
16
- let response = null;
17
- try {
18
- response = await page.goto(url, {timeout: timeout, waitUntil: "commit"});
19
- } catch (e) {
20
- logTimestamp('Error navigating to page');
21
- error = e
22
- continue
23
- }
24
- if (response && response.status() >= 400) {
25
- logTimestamp(`HTTP error ${response.status()} ${response.statusText()} while loading ${url}`);
26
- error = new Error('Could not load page.')
27
- continue
28
- }
83
+ const timeout = baseTimeout * i;
29
84
  try {
30
- await page.waitForLoadState('domcontentloaded', {timeout: timeout});
85
+ await attemptNavigation(currentPage, url, timeout);
86
+ break
31
87
  } catch (e) {
32
- logTimestamp('Error while waiting for DOMContentLoaded event');
33
88
  error = e
34
- continue
89
+ if (i < maxAttempts)
90
+ currentPage = await prepareRetry(currentPage, backoffMs * i, contextOptions);
35
91
  }
36
- try {
37
- await page.waitForLoadState('load', {timeout: timeout});
38
- } catch (e) {
39
- logTimestamp('Error while waiting for load event');
40
- error = e
41
- continue
42
- }
43
- break
44
92
  }
45
93
  if (error)
46
94
  throw error
95
+ return currentPage;
47
96
  }
48
97
 
49
98
 
@@ -62,11 +111,24 @@ function setCacheableUrlPattern(pattern) {
62
111
  cacheableUrlPatterns.push(pattern)
63
112
  }
64
113
 
65
- test.beforeEach(async ({ context }) => {
66
- startTimer();
67
- logTimestamp('Test execution started');
68
- await context.route('**/*', route => {
114
+ const cacheBustedResourceTypes = ['document', 'xhr', 'fetch'];
115
+
116
+ function isTestedSiteUrl(url) {
117
+ return baseUrl !== '' && url.href.includes(baseUrl);
118
+ }
119
+
120
+ async function configureContext(context) {
121
+ context.on('requestfailed', request => {
122
+ const errorText = request.failure()?.errorText;
123
+ logTimestamp(`Request failed: ${request.method()} ${request.url()} - ${errorText}`);
124
+ });
125
+ await context.route(isTestedSiteUrl, route => {
69
126
  const req = route.request();
127
+
128
+ if (!cacheBustedResourceTypes.includes(req.resourceType())) {
129
+ return route.continue();
130
+ }
131
+
70
132
  const url = new URL(req.url());
71
133
  const path = url.pathname.toLowerCase();
72
134
 
@@ -75,7 +137,7 @@ test.beforeEach(async ({ context }) => {
75
137
  return route.continue();
76
138
  }
77
139
 
78
- if (req.method() !== 'GET' || path.includes('/wp-content/') || !url.href.includes(baseUrl)) {
140
+ if (req.method() !== 'GET') {
79
141
  return route.continue();
80
142
  }
81
143
  url.searchParams.set('_cb', Date.now().toString());
@@ -83,6 +145,19 @@ test.beforeEach(async ({ context }) => {
83
145
  logTimestamp('Requesting ' + urlString);
84
146
  route.continue({ url: urlString });
85
147
  });
148
+ }
149
+
150
+ test.beforeEach(async ({ context }) => {
151
+ startTimer();
152
+ logTimestamp('Test execution started');
153
+ await configureContext(context);
154
+ });
155
+
156
+ test.afterEach(async () => {
157
+ const contextsToClose = replacementContexts.splice(0, replacementContexts.length);
158
+ for (const replacementContext of contextsToClose) {
159
+ await closeWithinTimeout(replacementContext, CLOSE_STUCK_TARGET_TIMEOUT_MS);
160
+ }
86
161
  });
87
162
 
88
163
  let testStartTime = null;