playwright 1.40.0 → 1.40.1
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/lib/common/test.js +10 -6
- package/lib/isomorphic/teleReceiver.js +10 -3
- package/lib/reporters/merge.js +17 -2
- package/lib/runner/dispatcher.js +36 -34
- package/lib/runner/failureTracker.js +1 -1
- package/package.json +2 -2
package/lib/common/test.js
CHANGED
|
@@ -198,9 +198,6 @@ class TestCase extends Base {
|
|
|
198
198
|
this._poolDigest = '';
|
|
199
199
|
this._workerHash = '';
|
|
200
200
|
this._projectId = '';
|
|
201
|
-
// This is different from |results.length| because sometimes we do not run the test, but consume
|
|
202
|
-
// an attempt, for example when skipping tests in a serial suite after a failure.
|
|
203
|
-
this._runAttempts = 0;
|
|
204
201
|
// Annotations known statically before running the test, e.g. `test.skip()` or `test.describe.skip()`.
|
|
205
202
|
this._staticAnnotations = [];
|
|
206
203
|
this.fn = fn;
|
|
@@ -213,9 +210,16 @@ class TestCase extends Base {
|
|
|
213
210
|
return titlePath;
|
|
214
211
|
}
|
|
215
212
|
outcome() {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
// Ignore initial skips that may be a result of "skipped because previous test in serial mode failed".
|
|
214
|
+
const results = [...this.results];
|
|
215
|
+
while (((_results$ = results[0]) === null || _results$ === void 0 ? void 0 : _results$.status) === 'skipped' || ((_results$2 = results[0]) === null || _results$2 === void 0 ? void 0 : _results$2.status) === 'interrupted') {
|
|
216
|
+
var _results$, _results$2;
|
|
217
|
+
results.shift();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// All runs were skipped.
|
|
221
|
+
if (!results.length) return 'skipped';
|
|
222
|
+
const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus);
|
|
219
223
|
if (!failures.length)
|
|
220
224
|
// all passed
|
|
221
225
|
return 'expected';
|
|
@@ -356,9 +356,16 @@ class TeleTestCase {
|
|
|
356
356
|
return titlePath;
|
|
357
357
|
}
|
|
358
358
|
outcome() {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
359
|
+
// Ignore initial skips that may be a result of "skipped because previous test in serial mode failed".
|
|
360
|
+
const results = [...this.results];
|
|
361
|
+
while (((_results$ = results[0]) === null || _results$ === void 0 ? void 0 : _results$.status) === 'skipped' || ((_results$2 = results[0]) === null || _results$2 === void 0 ? void 0 : _results$2.status) === 'interrupted') {
|
|
362
|
+
var _results$, _results$2;
|
|
363
|
+
results.shift();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// All runs were skipped.
|
|
367
|
+
if (!results.length) return 'skipped';
|
|
368
|
+
const failures = results.filter(result => result.status !== 'skipped' && result.status !== 'interrupted' && result.status !== this.expectedStatus);
|
|
362
369
|
if (!failures.length)
|
|
363
370
|
// all passed
|
|
364
371
|
return 'expected';
|
package/lib/reporters/merge.js
CHANGED
|
@@ -78,11 +78,26 @@ const commonEventNames = ['onBlobReportMetadata', 'onConfigure', 'onProject', 'o
|
|
|
78
78
|
const commonEvents = new Set(commonEventNames);
|
|
79
79
|
const commonEventRegex = new RegExp(`${commonEventNames.join('|')}`);
|
|
80
80
|
function parseCommonEvents(reportJsonl) {
|
|
81
|
-
return reportJsonl.
|
|
81
|
+
return splitBufferLines(reportJsonl).map(line => line.toString('utf8')).filter(line => commonEventRegex.test(line)) // quick filter
|
|
82
82
|
.map(line => JSON.parse(line)).filter(event => commonEvents.has(event.method));
|
|
83
83
|
}
|
|
84
84
|
function parseTestEvents(reportJsonl) {
|
|
85
|
-
return reportJsonl.
|
|
85
|
+
return splitBufferLines(reportJsonl).map(line => line.toString('utf8')).filter(line => line.length).map(line => JSON.parse(line)).filter(event => !commonEvents.has(event.method));
|
|
86
|
+
}
|
|
87
|
+
function splitBufferLines(buffer) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
let start = 0;
|
|
90
|
+
while (start < buffer.length) {
|
|
91
|
+
// 0x0A is the byte for '\n'
|
|
92
|
+
const end = buffer.indexOf(0x0A, start);
|
|
93
|
+
if (end === -1) {
|
|
94
|
+
lines.push(buffer.slice(start));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
lines.push(buffer.slice(start, end));
|
|
98
|
+
start = end + 1;
|
|
99
|
+
}
|
|
100
|
+
return lines;
|
|
86
101
|
}
|
|
87
102
|
async function extractAndParseReports(dir, shardFiles, internalizer, printStatus) {
|
|
88
103
|
const shardEvents = [];
|
package/lib/runner/dispatcher.js
CHANGED
|
@@ -283,7 +283,7 @@ class JobDispatcher {
|
|
|
283
283
|
test.expectedStatus = params.expectedStatus;
|
|
284
284
|
test.annotations = params.annotations;
|
|
285
285
|
test.timeout = params.timeout;
|
|
286
|
-
const isFailure = result.status !== test.expectedStatus;
|
|
286
|
+
const isFailure = result.status !== 'skipped' && result.status !== test.expectedStatus;
|
|
287
287
|
if (isFailure) this._failedTests.add(test);
|
|
288
288
|
this._reportTestEnd(test, result);
|
|
289
289
|
this._currentlyRunning = undefined;
|
|
@@ -364,16 +364,21 @@ class JobDispatcher {
|
|
|
364
364
|
}
|
|
365
365
|
result.errors = [...errors];
|
|
366
366
|
result.error = result.errors[0];
|
|
367
|
-
result.status = 'failed';
|
|
367
|
+
result.status = errors.length ? 'failed' : 'skipped';
|
|
368
368
|
this._reportTestEnd(test, result);
|
|
369
369
|
this._failedTests.add(test);
|
|
370
370
|
}
|
|
371
|
-
|
|
372
|
-
const test
|
|
373
|
-
|
|
374
|
-
this.
|
|
371
|
+
_massSkipTestsFromRemaining(testIds, errors) {
|
|
372
|
+
for (const test of this._remainingByTestId.values()) {
|
|
373
|
+
if (!testIds.has(test.id)) continue;
|
|
374
|
+
if (!this._failureTracker.hasReachedMaxFailures()) {
|
|
375
|
+
this._failTestWithErrors(test, errors);
|
|
376
|
+
errors = []; // Only report errors for the first test.
|
|
377
|
+
}
|
|
378
|
+
|
|
375
379
|
this._remainingByTestId.delete(test.id);
|
|
376
|
-
}
|
|
380
|
+
}
|
|
381
|
+
if (errors.length) {
|
|
377
382
|
// We had fatal errors after all tests have passed - most likely in some teardown.
|
|
378
383
|
// Let's just fail the test run.
|
|
379
384
|
this._failureTracker.onWorkerError();
|
|
@@ -401,26 +406,19 @@ class JobDispatcher {
|
|
|
401
406
|
}
|
|
402
407
|
}
|
|
403
408
|
if (params.fatalErrors.length) {
|
|
404
|
-
// In case of fatal errors, report
|
|
405
|
-
// and
|
|
406
|
-
this.
|
|
407
|
-
this._remainingByTestId.clear();
|
|
409
|
+
// In case of fatal errors, report first remaining test as failing with these errors,
|
|
410
|
+
// and all others as skipped.
|
|
411
|
+
this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), params.fatalErrors);
|
|
408
412
|
}
|
|
409
|
-
|
|
410
413
|
// Handle tests that should be skipped because of the setup failure.
|
|
411
|
-
|
|
414
|
+
this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []);
|
|
412
415
|
if (params.unexpectedExitError) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// worker failed to require the test file (at the start) because of an exception in one of imports.
|
|
420
|
-
// In this case, "skip" all remaining tests, to avoid running into the same exception over and over.
|
|
421
|
-
this._handleFatalErrors([params.unexpectedExitError]);
|
|
422
|
-
this._remainingByTestId.clear();
|
|
423
|
-
}
|
|
416
|
+
// When worker exits during a test, we blame the test itself.
|
|
417
|
+
//
|
|
418
|
+
// The most common situation when worker exits while not running a test is:
|
|
419
|
+
// worker failed to require the test file (at the start) because of an exception in one of imports.
|
|
420
|
+
// In this case, "skip" all remaining tests, to avoid running into the same exception over and over.
|
|
421
|
+
if (this._currentlyRunning) this._massSkipTestsFromRemaining(new Set([this._currentlyRunning.test.id]), [params.unexpectedExitError]);else this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), [params.unexpectedExitError]);
|
|
424
422
|
}
|
|
425
423
|
const retryCandidates = new Set();
|
|
426
424
|
const serialSuitesWithFailures = new Set();
|
|
@@ -432,20 +430,24 @@ class JobDispatcher {
|
|
|
432
430
|
}
|
|
433
431
|
if (outermostSerialSuite) serialSuitesWithFailures.add(outermostSerialSuite);
|
|
434
432
|
}
|
|
433
|
+
|
|
434
|
+
// If we have failed tests that belong to a serial suite,
|
|
435
|
+
// we should skip all future tests from the same serial suite.
|
|
436
|
+
const testsBelongingToSomeSerialSuiteWithFailures = [...this._remainingByTestId.values()].filter(test => {
|
|
437
|
+
let parent = test.parent;
|
|
438
|
+
while (parent && !serialSuitesWithFailures.has(parent)) parent = parent.parent;
|
|
439
|
+
return !!parent;
|
|
440
|
+
});
|
|
441
|
+
this._massSkipTestsFromRemaining(new Set(testsBelongingToSomeSerialSuiteWithFailures.map(test => test.id)), []);
|
|
435
442
|
for (const serialSuite of serialSuitesWithFailures) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
retryCandidates.add(test);
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
for (const test of this._job.tests) {
|
|
444
|
-
if (!this._remainingByTestId.has(test.id)) ++test._runAttempts;
|
|
443
|
+
// Add all tests from failed serial suites for possible retry.
|
|
444
|
+
// These will only be retried together, because they have the same
|
|
445
|
+
// "retries" setting and the same number of previous runs.
|
|
446
|
+
serialSuite.allTests().forEach(test => retryCandidates.add(test));
|
|
445
447
|
}
|
|
446
448
|
const remaining = [...this._remainingByTestId.values()];
|
|
447
449
|
for (const test of retryCandidates) {
|
|
448
|
-
if (test.
|
|
450
|
+
if (test.results.length < test.retries + 1) remaining.push(test);
|
|
449
451
|
}
|
|
450
452
|
|
|
451
453
|
// This job is over, we will schedule another one.
|
|
@@ -31,7 +31,7 @@ class FailureTracker {
|
|
|
31
31
|
this._rootSuite = rootSuite;
|
|
32
32
|
}
|
|
33
33
|
onTestEnd(test, result) {
|
|
34
|
-
if (result.status !== test.expectedStatus) ++this._failureCount;
|
|
34
|
+
if (result.status !== 'skipped' && result.status !== test.expectedStatus) ++this._failureCount;
|
|
35
35
|
}
|
|
36
36
|
onWorkerError() {
|
|
37
37
|
this._hasWorkerErrors = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.40.
|
|
3
|
+
"version": "1.40.1",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"license": "Apache-2.0",
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"playwright-core": "1.40.
|
|
57
|
+
"playwright-core": "1.40.1"
|
|
58
58
|
},
|
|
59
59
|
"optionalDependencies": {
|
|
60
60
|
"fsevents": "2.3.2"
|