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.
@@ -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
- const results = this.results.filter(result => result.status !== 'interrupted');
217
- if (results.every(result => result.status === 'skipped')) return 'skipped';
218
- const failures = results.filter(result => result.status !== this.expectedStatus);
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
- const results = this.results.filter(result => result.status !== 'interrupted');
360
- if (results.every(result => result.status === 'skipped')) return 'skipped';
361
- const failures = results.filter(result => result.status !== this.expectedStatus);
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';
@@ -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.toString().split('\n').filter(line => commonEventRegex.test(line)) // quick filter
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.toString().split('\n').filter(line => line.length).map(line => JSON.parse(line)).filter(event => !commonEvents.has(event.method));
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 = [];
@@ -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
- _handleFatalErrors(errors) {
372
- const test = this._remainingByTestId.values().next().value;
373
- if (test) {
374
- this._failTestWithErrors(test, errors);
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
- } else if (errors.length) {
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 the first remaining test as failing with these errors,
405
- // and "skip" all other tests to avoid running into the same issue over and over.
406
- this._handleFatalErrors(params.fatalErrors);
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
- for (const testId of params.skipTestsDueToSetupFailure) this._remainingByTestId.delete(testId);
414
+ this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []);
412
415
  if (params.unexpectedExitError) {
413
- if (this._currentlyRunning) {
414
- // When worker exits during a test, we blame the test itself.
415
- this._failTestWithErrors(this._currentlyRunning.test, [params.unexpectedExitError]);
416
- this._remainingByTestId.delete(this._currentlyRunning.test.id);
417
- } else {
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
- 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
- serialSuite.allTests().forEach(test => {
437
- // Skip remaining tests from serial suites with failures.
438
- this._remainingByTestId.delete(test.id);
439
- // Schedule them for the retry all together.
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._runAttempts < test.retries + 1) remaining.push(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.0",
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.0"
57
+ "playwright-core": "1.40.1"
58
58
  },
59
59
  "optionalDependencies": {
60
60
  "fsevents": "2.3.2"