+202
@@ -0,0 +1,202 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Portions Copyright (c) Microsoft Corporation.
|
||||
Portions Copyright 2017 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
Playwright
|
||||
Copyright (c) Microsoft Corporation
|
||||
|
||||
This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer),
|
||||
available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE).
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
# 🎭 Playwright
|
||||
|
||||
[](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge -->[](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge -->[](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> <!-- GEN:webkit-version-badge -->[](https://webkit.org/)<!-- GEN:stop --> [](https://aka.ms/playwright/discord)
|
||||
|
||||
## [Documentation](https://playwright.dev) | [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
Playwright is a framework for Web Testing and Automation. It allows testing [Chromium](https://www.chromium.org/Home)<sup>1</sup>, [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable**, and **fast**.
|
||||
|
||||
| | Linux | macOS | Windows |
|
||||
| :--- | :---: | :---: | :---: |
|
||||
| Chromium<sup>1</sup> <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
|
||||
|
||||
Headless execution is supported for all browsers on all platforms. Check out [system requirements](https://playwright.dev/docs/intro#system-requirements) for details.
|
||||
|
||||
Looking for Playwright for [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)?
|
||||
|
||||
<sup>1</sup> Playwright uses [Chrome for Testing](https://developer.chrome.com/blog/chrome-for-testing) by default.
|
||||
|
||||
## Installation
|
||||
|
||||
Playwright has its own test runner for end-to-end tests, we call it Playwright Test.
|
||||
|
||||
### Using init command
|
||||
|
||||
The easiest way to get started with Playwright Test is to run the init command.
|
||||
|
||||
```Shell
|
||||
# Run from your project's root directory
|
||||
npm init playwright@latest
|
||||
# Or create a new project
|
||||
npm init playwright@latest new-project
|
||||
```
|
||||
|
||||
This will create a configuration file, optionally add examples, a GitHub Action workflow and a first test example.spec.ts. You can now jump directly to writing assertions section.
|
||||
|
||||
### Manually
|
||||
|
||||
Add dependency and install browsers.
|
||||
|
||||
```Shell
|
||||
npm i -D @playwright/test
|
||||
# install supported browsers
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
You can optionally install only selected browsers, see [install browsers](https://playwright.dev/docs/cli#install-browsers) for more details. Or you can install no browsers at all and use existing [browser channels](https://playwright.dev/docs/browsers).
|
||||
|
||||
* [Getting started](https://playwright.dev/docs/intro)
|
||||
* [API reference](https://playwright.dev/docs/api/class-playwright)
|
||||
|
||||
## Capabilities
|
||||
|
||||
### Resilient • No flaky tests
|
||||
|
||||
**Auto-wait**. Playwright waits for elements to be actionable prior to performing actions. It also has a rich set of introspection events. The combination of the two eliminates the need for artificial timeouts - a primary cause of flaky tests.
|
||||
|
||||
**Web-first assertions**. Playwright assertions are created specifically for the dynamic web. Checks are automatically retried until the necessary conditions are met.
|
||||
|
||||
**Tracing**. Configure test retry strategy, capture execution trace, videos and screenshots to eliminate flakes.
|
||||
|
||||
### No trade-offs • No limits
|
||||
|
||||
Browsers run web content belonging to different origins in different processes. Playwright is aligned with the architecture of the modern browsers and runs tests out-of-process. This makes Playwright free of the typical in-process test runner limitations.
|
||||
|
||||
**Multiple everything**. Test scenarios that span multiple tabs, multiple origins and multiple users. Create scenarios with different contexts for different users and run them against your server, all in one test.
|
||||
|
||||
**Trusted events**. Hover elements, interact with dynamic controls and produce trusted events. Playwright uses real browser input pipeline indistinguishable from the real user.
|
||||
|
||||
Test frames, pierce Shadow DOM. Playwright selectors pierce shadow DOM and allow entering frames seamlessly.
|
||||
|
||||
### Full isolation • Fast execution
|
||||
|
||||
**Browser contexts**. Playwright creates a browser context for each test. Browser context is equivalent to a brand new browser profile. This delivers full test isolation with zero overhead. Creating a new browser context only takes a handful of milliseconds.
|
||||
|
||||
**Log in once**. Save the authentication state of the context and reuse it in all the tests. This bypasses repetitive log-in operations in each test, yet delivers full isolation of independent tests.
|
||||
|
||||
### Powerful Tooling
|
||||
|
||||
**[Codegen](https://playwright.dev/docs/codegen)**. Generate tests by recording your actions. Save them into any language.
|
||||
|
||||
**[Playwright inspector](https://playwright.dev/docs/inspector)**. Inspect page, generate selectors, step through the test execution, see click points and explore execution logs.
|
||||
|
||||
**[Trace Viewer](https://playwright.dev/docs/trace-viewer)**. Capture all the information to investigate the test failure. Playwright trace contains test execution screencast, live DOM snapshots, action explorer, test source and many more.
|
||||
|
||||
Looking for Playwright for [TypeScript](https://playwright.dev/docs/intro), [JavaScript](https://playwright.dev/docs/intro), [Python](https://playwright.dev/python/docs/intro), [.NET](https://playwright.dev/dotnet/docs/intro), or [Java](https://playwright.dev/java/docs/intro)?
|
||||
|
||||
## Examples
|
||||
|
||||
To learn how to run these Playwright Test examples, check out our [getting started docs](https://playwright.dev/docs/intro).
|
||||
|
||||
#### Page screenshot
|
||||
|
||||
This code snippet navigates to Playwright homepage and saves a screenshot.
|
||||
|
||||
```TypeScript
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('Page Screenshot', async ({ page }) => {
|
||||
await page.goto('https://playwright.dev/');
|
||||
await page.screenshot({ path: `example.png` });
|
||||
});
|
||||
```
|
||||
|
||||
#### Mobile and geolocation
|
||||
|
||||
This snippet emulates Mobile Safari on a device at given geolocation, navigates to maps.google.com, performs the action and takes a screenshot.
|
||||
|
||||
```TypeScript
|
||||
import { test, devices } from '@playwright/test';
|
||||
|
||||
test.use({
|
||||
...devices['iPhone 13 Pro'],
|
||||
locale: 'en-US',
|
||||
geolocation: { longitude: 12.492507, latitude: 41.889938 },
|
||||
permissions: ['geolocation'],
|
||||
})
|
||||
|
||||
test('Mobile and geolocation', async ({ page }) => {
|
||||
await page.goto('https://maps.google.com');
|
||||
await page.getByText('Your location').click();
|
||||
await page.waitForRequest(/.*preview\/pwa/);
|
||||
await page.screenshot({ path: 'colosseum-iphone.png' });
|
||||
});
|
||||
```
|
||||
|
||||
#### Evaluate in browser context
|
||||
|
||||
This code snippet navigates to example.com, and executes a script in the page context.
|
||||
|
||||
```TypeScript
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('Evaluate in browser context', async ({ page }) => {
|
||||
await page.goto('https://www.example.com/');
|
||||
const dimensions = await page.evaluate(() => {
|
||||
return {
|
||||
width: document.documentElement.clientWidth,
|
||||
height: document.documentElement.clientHeight,
|
||||
deviceScaleFactor: window.devicePixelRatio
|
||||
}
|
||||
});
|
||||
console.log(dimensions);
|
||||
});
|
||||
```
|
||||
|
||||
#### Intercept network requests
|
||||
|
||||
This code snippet sets up request routing for a page to log all network requests.
|
||||
|
||||
```TypeScript
|
||||
import { test } from '@playwright/test';
|
||||
|
||||
test('Intercept network requests', async ({ page }) => {
|
||||
// Log and continue all network requests
|
||||
await page.route('**', route => {
|
||||
console.log(route.request().url());
|
||||
route.continue();
|
||||
});
|
||||
await page.goto('http://todomvc.com');
|
||||
});
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
* [Documentation](https://playwright.dev)
|
||||
* [API reference](https://playwright.dev/docs/api/class-playwright/)
|
||||
* [Contribution guide](CONTRIBUTING.md)
|
||||
* [Changelog](https://github.com/microsoft/playwright/releases)
|
||||
+3919
File diff suppressed because it is too large
Load Diff
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const { program } = require('./lib/program');
|
||||
program.parse(process.argv);
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export * from 'playwright-core';
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = require('playwright-core');
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export * from 'playwright-core';
|
||||
import playwright from 'playwright-core';
|
||||
export default playwright;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
function jsx(type, props, key) {
|
||||
return {
|
||||
__pw_type: 'jsx',
|
||||
type,
|
||||
props,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
function jsxs(type, props, key) {
|
||||
return {
|
||||
__pw_type: 'jsx',
|
||||
type,
|
||||
props,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
||||
// this is used in <></> notation
|
||||
const Fragment = { __pw_jsx_fragment: true };
|
||||
|
||||
module.exports = {
|
||||
Fragment,
|
||||
jsx,
|
||||
jsxs,
|
||||
};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import jsxRuntime from './jsx-runtime.js';
|
||||
|
||||
export const jsx = jsxRuntime.jsx;
|
||||
export const jsxs = jsxRuntime.jsxs;
|
||||
export const Fragment = jsxRuntime.Fragment;
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var agentParser_exports = {};
|
||||
__export(agentParser_exports, {
|
||||
parseAgentSpec: () => parseAgentSpec
|
||||
});
|
||||
module.exports = __toCommonJS(agentParser_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
async function parseAgentSpec(filePath) {
|
||||
const source = await import_fs.default.promises.readFile(filePath, "utf-8");
|
||||
const { header, content } = extractYamlAndContent(source);
|
||||
const { instructions, examples } = extractInstructionsAndExamples(content);
|
||||
return {
|
||||
...header,
|
||||
instructions,
|
||||
examples
|
||||
};
|
||||
}
|
||||
function extractYamlAndContent(markdown) {
|
||||
const lines = markdown.split("\n");
|
||||
if (lines[0] !== "---")
|
||||
throw new Error("Markdown file must start with YAML front matter (---)");
|
||||
let yamlEndIndex = -1;
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
if (lines[i] === "---") {
|
||||
yamlEndIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (yamlEndIndex === -1)
|
||||
throw new Error("YAML front matter must be closed with ---");
|
||||
const yamlLines = lines.slice(1, yamlEndIndex);
|
||||
const yamlRaw = yamlLines.join("\n");
|
||||
const contentLines = lines.slice(yamlEndIndex + 1);
|
||||
const content = contentLines.join("\n");
|
||||
let header;
|
||||
try {
|
||||
header = import_utilsBundle.yaml.parse(yamlRaw);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse YAML header: ${error.message}`);
|
||||
}
|
||||
if (!header.name)
|
||||
throw new Error('YAML header must contain a "name" field');
|
||||
if (!header.description)
|
||||
throw new Error('YAML header must contain a "description" field');
|
||||
return { header, content };
|
||||
}
|
||||
function extractInstructionsAndExamples(content) {
|
||||
const examples = [];
|
||||
const instructions = content.split("<example>")[0].trim();
|
||||
const exampleRegex = /<example>([\s\S]*?)<\/example>/g;
|
||||
let match;
|
||||
while ((match = exampleRegex.exec(content)) !== null) {
|
||||
const example = match[1].trim();
|
||||
examples.push(example.replace(/[\n]/g, " ").replace(/ +/g, " "));
|
||||
}
|
||||
return { instructions, examples };
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
parseAgentSpec
|
||||
});
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
name: "Copilot Setup Steps"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/copilot-setup-steps.yml
|
||||
|
||||
jobs:
|
||||
copilot-setup-steps:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
# Customize this step as needed
|
||||
- name: Build application
|
||||
run: npx run build
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var generateAgents_exports = {};
|
||||
__export(generateAgents_exports, {
|
||||
ClaudeGenerator: () => ClaudeGenerator,
|
||||
CopilotGenerator: () => CopilotGenerator,
|
||||
OpencodeGenerator: () => OpencodeGenerator,
|
||||
VSCodeGenerator: () => VSCodeGenerator
|
||||
});
|
||||
module.exports = __toCommonJS(generateAgents_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_seed = require("../mcp/test/seed");
|
||||
var import_agentParser = require("./agentParser");
|
||||
async function loadAgentSpecs() {
|
||||
const files = await import_fs.default.promises.readdir(__dirname);
|
||||
return Promise.all(files.filter((file) => file.endsWith(".agent.md")).map((file) => (0, import_agentParser.parseAgentSpec)(import_path.default.join(__dirname, file))));
|
||||
}
|
||||
class ClaudeGenerator {
|
||||
static async init(config, projectName, prompts) {
|
||||
await initRepo(config, projectName, {
|
||||
promptsFolder: prompts ? ".claude/prompts" : void 0
|
||||
});
|
||||
const agents = await loadAgentSpecs();
|
||||
await import_fs.default.promises.mkdir(".claude/agents", { recursive: true });
|
||||
for (const agent of agents)
|
||||
await writeFile(`.claude/agents/${agent.name}.md`, ClaudeGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
|
||||
const mcpServer = process.platform === "win32" ? { command: "cmd", args: ["/c", "npx", "playwright", "run-test-mcp-server"] } : { command: "npx", args: ["playwright", "run-test-mcp-server"] };
|
||||
await writeFile(".mcp.json", JSON.stringify({
|
||||
mcpServers: {
|
||||
"playwright-test": mcpServer
|
||||
}
|
||||
}, null, 2), "\u{1F527}", "mcp configuration");
|
||||
initRepoDone();
|
||||
}
|
||||
static agentSpec(agent) {
|
||||
const claudeToolMap = /* @__PURE__ */ new Map([
|
||||
["search", ["Glob", "Grep", "Read", "LS"]],
|
||||
["edit", ["Edit", "MultiEdit", "Write"]]
|
||||
]);
|
||||
function asClaudeTool(tool) {
|
||||
const [first, second] = tool.split("/");
|
||||
if (!second)
|
||||
return (claudeToolMap.get(first) || [first]).join(", ");
|
||||
return `mcp__${first}__${second}`;
|
||||
}
|
||||
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
|
||||
const lines = [];
|
||||
const header = {
|
||||
name: agent.name,
|
||||
description: agent.description + examples,
|
||||
tools: agent.tools.map((tool) => asClaudeTool(tool)).join(", "),
|
||||
model: agent.model,
|
||||
color: agent.color
|
||||
};
|
||||
lines.push(`---`);
|
||||
lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`);
|
||||
lines.push("");
|
||||
lines.push(agent.instructions);
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
class OpencodeGenerator {
|
||||
static async init(config, projectName, prompts) {
|
||||
await initRepo(config, projectName, {
|
||||
defaultAgentName: "Build",
|
||||
promptsFolder: prompts ? ".opencode/prompts" : void 0
|
||||
});
|
||||
const agents = await loadAgentSpecs();
|
||||
for (const agent of agents) {
|
||||
const prompt = [agent.instructions];
|
||||
prompt.push("");
|
||||
prompt.push(...agent.examples.map((example) => `<example>${example}</example>`));
|
||||
await writeFile(`.opencode/prompts/${agent.name}.md`, prompt.join("\n"), "\u{1F916}", "agent definition");
|
||||
}
|
||||
await writeFile("opencode.json", OpencodeGenerator.configuration(agents), "\u{1F527}", "opencode configuration");
|
||||
initRepoDone();
|
||||
}
|
||||
static configuration(agents) {
|
||||
const opencodeToolMap = /* @__PURE__ */ new Map([
|
||||
["search", ["ls", "glob", "grep", "read"]],
|
||||
["edit", ["edit", "write"]]
|
||||
]);
|
||||
const asOpencodeTool = (tools, tool) => {
|
||||
const [first, second] = tool.split("/");
|
||||
if (!second) {
|
||||
for (const tool2 of opencodeToolMap.get(first) || [first])
|
||||
tools[tool2] = true;
|
||||
} else {
|
||||
tools[`${first}*${second}`] = true;
|
||||
}
|
||||
};
|
||||
const result = {};
|
||||
result["$schema"] = "https://opencode.ai/config.json";
|
||||
result["mcp"] = {};
|
||||
result["tools"] = {
|
||||
"playwright*": false
|
||||
};
|
||||
result["agent"] = {};
|
||||
for (const agent of agents) {
|
||||
const tools = {};
|
||||
result["agent"][agent.name] = {
|
||||
description: agent.description,
|
||||
mode: "subagent",
|
||||
prompt: `{file:.opencode/prompts/${agent.name}.md}`,
|
||||
tools
|
||||
};
|
||||
for (const tool of agent.tools)
|
||||
asOpencodeTool(tools, tool);
|
||||
}
|
||||
result["mcp"]["playwright-test"] = {
|
||||
type: "local",
|
||||
command: ["npx", "playwright", "run-test-mcp-server"],
|
||||
enabled: true
|
||||
};
|
||||
return JSON.stringify(result, null, 2);
|
||||
}
|
||||
}
|
||||
class CopilotGenerator {
|
||||
static async init(config, projectName, prompts) {
|
||||
await initRepo(config, projectName, {
|
||||
defaultAgentName: "agent",
|
||||
promptsFolder: prompts ? ".github/prompts" : void 0,
|
||||
promptSuffix: "prompt"
|
||||
});
|
||||
const agents = await loadAgentSpecs();
|
||||
await import_fs.default.promises.mkdir(".github/agents", { recursive: true });
|
||||
for (const agent of agents)
|
||||
await writeFile(`.github/agents/${agent.name}.agent.md`, CopilotGenerator.agentSpec(agent), "\u{1F916}", "agent definition");
|
||||
await deleteFile(`.github/chatmodes/ \u{1F3AD} planner.chatmode.md`, "legacy planner chatmode");
|
||||
await deleteFile(`.github/chatmodes/\u{1F3AD} generator.chatmode.md`, "legacy generator chatmode");
|
||||
await deleteFile(`.github/chatmodes/\u{1F3AD} healer.chatmode.md`, "legacy healer chatmode");
|
||||
await deleteFile(`.github/agents/ \u{1F3AD} planner.agent.md`, "legacy planner agent");
|
||||
await deleteFile(`.github/agents/\u{1F3AD} generator.agent.md`, "legacy generator agent");
|
||||
await deleteFile(`.github/agents/\u{1F3AD} healer.agent.md`, "legacy healer agent");
|
||||
await VSCodeGenerator.appendToMCPJson();
|
||||
const mcpConfig = { mcpServers: CopilotGenerator.mcpServers };
|
||||
if (!import_fs.default.existsSync(".github/copilot-setup-steps.yml")) {
|
||||
const yaml2 = import_fs.default.readFileSync(import_path.default.join(__dirname, "copilot-setup-steps.yml"), "utf-8");
|
||||
await writeFile(".github/workflows/copilot-setup-steps.yml", yaml2, "\u{1F527}", "GitHub Copilot setup steps");
|
||||
}
|
||||
console.log("");
|
||||
console.log("");
|
||||
console.log(" \u{1F527} TODO: GitHub > Settings > Copilot > Coding agent > MCP configuration");
|
||||
console.log("------------------------------------------------------------------");
|
||||
console.log(JSON.stringify(mcpConfig, null, 2));
|
||||
console.log("------------------------------------------------------------------");
|
||||
initRepoDone();
|
||||
}
|
||||
static agentSpec(agent) {
|
||||
const examples = agent.examples.length ? ` Examples: ${agent.examples.map((example) => `<example>${example}</example>`).join("")}` : "";
|
||||
const lines = [];
|
||||
const header = {
|
||||
"name": agent.name,
|
||||
"description": agent.description + examples,
|
||||
"tools": agent.tools,
|
||||
"model": "Claude Sonnet 4",
|
||||
"mcp-servers": CopilotGenerator.mcpServers
|
||||
};
|
||||
lines.push(`---`);
|
||||
lines.push(import_utilsBundle.yaml.stringify(header, { lineWidth: 1e5 }) + `---`);
|
||||
lines.push("");
|
||||
lines.push(agent.instructions);
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
static {
|
||||
this.mcpServers = {
|
||||
"playwright-test": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"playwright",
|
||||
"run-test-mcp-server"
|
||||
],
|
||||
"tools": ["*"]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
class VSCodeGenerator {
|
||||
static async init(config, projectName) {
|
||||
await initRepo(config, projectName, {
|
||||
promptsFolder: void 0
|
||||
});
|
||||
const agents = await loadAgentSpecs();
|
||||
const nameMap = /* @__PURE__ */ new Map([
|
||||
["playwright-test-planner", " \u{1F3AD} planner"],
|
||||
["playwright-test-generator", "\u{1F3AD} generator"],
|
||||
["playwright-test-healer", "\u{1F3AD} healer"]
|
||||
]);
|
||||
await import_fs.default.promises.mkdir(".github/chatmodes", { recursive: true });
|
||||
for (const agent of agents)
|
||||
await writeFile(`.github/chatmodes/${nameMap.get(agent.name)}.chatmode.md`, VSCodeGenerator.agentSpec(agent), "\u{1F916}", "chatmode definition");
|
||||
await VSCodeGenerator.appendToMCPJson();
|
||||
initRepoDone();
|
||||
}
|
||||
static async appendToMCPJson() {
|
||||
await import_fs.default.promises.mkdir(".vscode", { recursive: true });
|
||||
const mcpJsonPath = ".vscode/mcp.json";
|
||||
let mcpJson = {
|
||||
servers: {},
|
||||
inputs: []
|
||||
};
|
||||
try {
|
||||
mcpJson = JSON.parse(import_fs.default.readFileSync(mcpJsonPath, "utf8"));
|
||||
} catch {
|
||||
}
|
||||
if (!mcpJson.servers)
|
||||
mcpJson.servers = {};
|
||||
mcpJson.servers["playwright-test"] = {
|
||||
type: "stdio",
|
||||
command: "npx",
|
||||
args: ["playwright", "run-test-mcp-server"]
|
||||
};
|
||||
await writeFile(mcpJsonPath, JSON.stringify(mcpJson, null, 2), "\u{1F527}", "mcp configuration");
|
||||
}
|
||||
static agentSpec(agent) {
|
||||
const vscodeToolMap = /* @__PURE__ */ new Map([
|
||||
["search", ["search/listDirectory", "search/fileSearch", "search/textSearch"]],
|
||||
["read", ["search/readFile"]],
|
||||
["edit", ["edit/editFiles"]],
|
||||
["write", ["edit/createFile", "edit/createDirectory"]]
|
||||
]);
|
||||
const vscodeToolsOrder = ["edit/createFile", "edit/createDirectory", "edit/editFiles", "search/fileSearch", "search/textSearch", "search/listDirectory", "search/readFile"];
|
||||
const vscodeMcpName = "playwright-test";
|
||||
function asVscodeTool(tool) {
|
||||
const [first, second] = tool.split("/");
|
||||
if (second)
|
||||
return `${vscodeMcpName}/${second}`;
|
||||
return vscodeToolMap.get(first) || first;
|
||||
}
|
||||
const tools = agent.tools.map(asVscodeTool).flat().sort((a, b) => {
|
||||
const indexA = vscodeToolsOrder.indexOf(a);
|
||||
const indexB = vscodeToolsOrder.indexOf(b);
|
||||
if (indexA === -1 && indexB === -1)
|
||||
return a.localeCompare(b);
|
||||
if (indexA === -1)
|
||||
return 1;
|
||||
if (indexB === -1)
|
||||
return -1;
|
||||
return indexA - indexB;
|
||||
}).map((tool) => `'${tool}'`).join(", ");
|
||||
const lines = [];
|
||||
lines.push(`---`);
|
||||
lines.push(`description: ${agent.description}.`);
|
||||
lines.push(`tools: [${tools}]`);
|
||||
lines.push(`---`);
|
||||
lines.push("");
|
||||
lines.push(agent.instructions);
|
||||
for (const example of agent.examples)
|
||||
lines.push(`<example>${example}</example>`);
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
}
|
||||
async function writeFile(filePath, content, icon, description) {
|
||||
console.log(` ${icon} ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
||||
await (0, import_utils.mkdirIfNeeded)(filePath);
|
||||
await import_fs.default.promises.writeFile(filePath, content, "utf-8");
|
||||
}
|
||||
async function deleteFile(filePath, description) {
|
||||
try {
|
||||
if (!import_fs.default.existsSync(filePath))
|
||||
return;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
console.log(` \u2702\uFE0F ${import_path.default.relative(process.cwd(), filePath)} ${import_utilsBundle.colors.dim("- " + description)}`);
|
||||
await import_fs.default.promises.unlink(filePath);
|
||||
}
|
||||
async function initRepo(config, projectName, options) {
|
||||
const project = (0, import_seed.seedProject)(config, projectName);
|
||||
console.log(` \u{1F3AD} Using project "${project.project.name}" as a primary project`);
|
||||
if (!import_fs.default.existsSync("specs")) {
|
||||
await import_fs.default.promises.mkdir("specs");
|
||||
await writeFile(import_path.default.join("specs", "README.md"), `# Specs
|
||||
|
||||
This is a directory for test plans.
|
||||
`, "\u{1F4DD}", "directory for test plans");
|
||||
}
|
||||
let seedFile = await (0, import_seed.findSeedFile)(project);
|
||||
if (!seedFile) {
|
||||
seedFile = (0, import_seed.defaultSeedFile)(project);
|
||||
await writeFile(seedFile, import_seed.seedFileContent, "\u{1F331}", "default environment seed file");
|
||||
}
|
||||
if (options.promptsFolder) {
|
||||
await import_fs.default.promises.mkdir(options.promptsFolder, { recursive: true });
|
||||
for (const promptFile of await import_fs.default.promises.readdir(__dirname)) {
|
||||
if (!promptFile.endsWith(".prompt.md"))
|
||||
continue;
|
||||
const shortName = promptFile.replace(".prompt.md", "");
|
||||
const fileName = options.promptSuffix ? `${shortName}.${options.promptSuffix}.md` : `${shortName}.md`;
|
||||
const content = await loadPrompt(promptFile, {
|
||||
defaultAgentName: "default",
|
||||
...options,
|
||||
seedFile: import_path.default.relative(process.cwd(), seedFile)
|
||||
});
|
||||
await writeFile(import_path.default.join(options.promptsFolder, fileName), content, "\u{1F4DD}", "prompt template");
|
||||
}
|
||||
}
|
||||
}
|
||||
function initRepoDone() {
|
||||
console.log(" \u2705 Done.");
|
||||
}
|
||||
async function loadPrompt(file, params) {
|
||||
const content = await import_fs.default.promises.readFile(import_path.default.join(__dirname, file), "utf-8");
|
||||
return Object.entries(params).reduce((acc, [key, value]) => {
|
||||
return acc.replace(new RegExp(`\\\${${key}}`, "g"), value);
|
||||
}, content);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
ClaudeGenerator,
|
||||
CopilotGenerator,
|
||||
OpencodeGenerator,
|
||||
VSCodeGenerator
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
---
|
||||
agent: ${defaultAgentName}
|
||||
description: Produce test coverage
|
||||
---
|
||||
|
||||
Parameters:
|
||||
- Task: the task to perform
|
||||
- Seed file (optional): the seed file to use, defaults to `${seedFile}`
|
||||
- Test plan file (optional): the test plan file to write, under `specs/` folder.
|
||||
|
||||
1. Call #playwright-test-planner subagent with prompt:
|
||||
|
||||
<plan>
|
||||
<task-text><!-- the task --></task-text>
|
||||
<seed-file><!-- path to seed file --></seed-file>
|
||||
<plan-file><!-- path to test plan file to generate --></plan-file>
|
||||
</plan>
|
||||
|
||||
2. For each test case from the test plan file (1.1, 1.2, ...), one after another, not in parallel, call #playwright-test-generator subagent with prompt:
|
||||
|
||||
<generate>
|
||||
<test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
|
||||
<test-name><!-- Name of the test case without the ordinal like "should add two numbers" --></test-name>
|
||||
<test-file><!-- Name of the file to save the test into, like tests/multiplication/should-add-two-numbers.spec.ts --></test-file>
|
||||
<seed-file><!-- Seed file path from test plan --></seed-file>
|
||||
<body><!-- Test case content including steps and expectations --></body>
|
||||
</generate>
|
||||
|
||||
3. Call #playwright-test-healer subagent with prompt:
|
||||
|
||||
<heal>Run all tests and fix the failing ones one after another.</heal>
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
---
|
||||
agent: playwright-test-generator
|
||||
description: Generate test plan
|
||||
---
|
||||
|
||||
Generate tests for the test plan's bullet 1.1 Add item to card.
|
||||
|
||||
Test plan: `specs/coverage.plan.md`
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: playwright-test-generator
|
||||
description: Use this agent when you need to create automated browser tests using Playwright
|
||||
model: sonnet
|
||||
color: blue
|
||||
tools:
|
||||
- search
|
||||
- playwright-test/browser_click
|
||||
- playwright-test/browser_drag
|
||||
- playwright-test/browser_evaluate
|
||||
- playwright-test/browser_file_upload
|
||||
- playwright-test/browser_handle_dialog
|
||||
- playwright-test/browser_hover
|
||||
- playwright-test/browser_navigate
|
||||
- playwright-test/browser_press_key
|
||||
- playwright-test/browser_select_option
|
||||
- playwright-test/browser_snapshot
|
||||
- playwright-test/browser_type
|
||||
- playwright-test/browser_verify_element_visible
|
||||
- playwright-test/browser_verify_list_visible
|
||||
- playwright-test/browser_verify_text_visible
|
||||
- playwright-test/browser_verify_value
|
||||
- playwright-test/browser_wait_for
|
||||
- playwright-test/generator_read_log
|
||||
- playwright-test/generator_setup_page
|
||||
- playwright-test/generator_write_test
|
||||
---
|
||||
|
||||
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
|
||||
Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
|
||||
application behavior.
|
||||
|
||||
# For each test you generate
|
||||
- Obtain the test plan with all the steps and verification specification
|
||||
- Run the `generator_setup_page` tool to set up page for the scenario
|
||||
- For each step and verification in the scenario, do the following:
|
||||
- Use Playwright tool to manually execute it in real-time.
|
||||
- Use the step description as the intent for each Playwright tool call.
|
||||
- Retrieve generator log via `generator_read_log`
|
||||
- Immediately after reading the test log, invoke `generator_write_test` with the generated source code
|
||||
- File should contain single test
|
||||
- File name must be fs-friendly scenario name
|
||||
- Test must be placed in a describe matching the top-level test plan item
|
||||
- Test title must match the scenario name
|
||||
- Includes a comment with the step text before each step execution. Do not duplicate comments if step requires
|
||||
multiple actions.
|
||||
- Always use best practices from the log when generating tests.
|
||||
|
||||
<example-generation>
|
||||
For following plan:
|
||||
|
||||
```markdown file=specs/plan.md
|
||||
### 1. Adding New Todos
|
||||
**Seed:** `tests/seed.spec.ts`
|
||||
|
||||
#### 1.1 Add Valid Todo
|
||||
**Steps:**
|
||||
1. Click in the "What needs to be done?" input field
|
||||
|
||||
#### 1.2 Add Multiple Todos
|
||||
...
|
||||
```
|
||||
|
||||
Following file is generated:
|
||||
|
||||
```ts file=add-valid-todo.spec.ts
|
||||
// spec: specs/plan.md
|
||||
// seed: tests/seed.spec.ts
|
||||
|
||||
test.describe('Adding New Todos', () => {
|
||||
test('Add Valid Todo', async { page } => {
|
||||
// 1. Click in the "What needs to be done?" input field
|
||||
await page.click(...);
|
||||
|
||||
...
|
||||
});
|
||||
});
|
||||
```
|
||||
</example-generation>
|
||||
|
||||
<example>
|
||||
Context: User wants to generate a test for the test plan item.
|
||||
<test-suite><!-- Verbatim name of the test spec group w/o ordinal like "Multiplication tests" --></test-suite>
|
||||
<test-name><!-- Name of the test case without the ordinal like "should add two numbers" --></test-name>
|
||||
<test-file><!-- Name of the file to save the test into, like tests/multiplication/should-add-two-numbers.spec.ts --></test-file>
|
||||
<seed-file><!-- Seed file path from test plan --></seed-file>
|
||||
<body><!-- Test case content including steps and expectations --></body>
|
||||
</example>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
---
|
||||
agent: playwright-test-healer
|
||||
description: Fix tests
|
||||
---
|
||||
|
||||
Run all my tests and fix the failing ones.
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
---
|
||||
name: playwright-test-healer
|
||||
description: Use this agent when you need to debug and fix failing Playwright tests
|
||||
model: sonnet
|
||||
color: red
|
||||
tools:
|
||||
- search
|
||||
- edit
|
||||
- playwright-test/browser_console_messages
|
||||
- playwright-test/browser_evaluate
|
||||
- playwright-test/browser_generate_locator
|
||||
- playwright-test/browser_network_requests
|
||||
- playwright-test/browser_snapshot
|
||||
- playwright-test/test_debug
|
||||
- playwright-test/test_list
|
||||
- playwright-test/test_run
|
||||
---
|
||||
|
||||
You are the Playwright Test Healer, an expert test automation engineer specializing in debugging and
|
||||
resolving Playwright test failures. Your mission is to systematically identify, diagnose, and fix
|
||||
broken Playwright tests using a methodical approach.
|
||||
|
||||
Your workflow:
|
||||
1. **Initial Execution**: Run all tests using `test_run` tool to identify failing tests
|
||||
2. **Debug failed tests**: For each failing test run `test_debug`.
|
||||
3. **Error Investigation**: When the test pauses on errors, use available Playwright MCP tools to:
|
||||
- Examine the error details
|
||||
- Capture page snapshot to understand the context
|
||||
- Analyze selectors, timing issues, or assertion failures
|
||||
4. **Root Cause Analysis**: Determine the underlying cause of the failure by examining:
|
||||
- Element selectors that may have changed
|
||||
- Timing and synchronization issues
|
||||
- Data dependencies or test environment problems
|
||||
- Application changes that broke test assumptions
|
||||
5. **Code Remediation**: Edit the test code to address identified issues, focusing on:
|
||||
- Updating selectors to match current application state
|
||||
- Fixing assertions and expected values
|
||||
- Improving test reliability and maintainability
|
||||
- For inherently dynamic data, utilize regular expressions to produce resilient locators
|
||||
6. **Verification**: Restart the test after each fix to validate the changes
|
||||
7. **Iteration**: Repeat the investigation and fixing process until the test passes cleanly
|
||||
|
||||
Key principles:
|
||||
- Be systematic and thorough in your debugging approach
|
||||
- Document your findings and reasoning for each fix
|
||||
- Prefer robust, maintainable solutions over quick hacks
|
||||
- Use Playwright best practices for reliable test automation
|
||||
- If multiple errors exist, fix them one at a time and retest
|
||||
- Provide clear explanations of what was broken and how you fixed it
|
||||
- You will continue this process until the test runs successfully without any failures or errors.
|
||||
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
|
||||
so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
|
||||
of the expected behavior.
|
||||
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
|
||||
- Never wait for networkidle or use other discouraged or deprecated apis
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
---
|
||||
agent: playwright-test-planner
|
||||
description: Create test plan
|
||||
---
|
||||
|
||||
Create test plan for "add to cart" functionality of my app.
|
||||
|
||||
- Seed file: `${seedFile}`
|
||||
- Test plan: `specs/coverage.plan.md`
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
---
|
||||
name: playwright-test-planner
|
||||
description: Use this agent when you need to create comprehensive test plan for a web application or website
|
||||
model: sonnet
|
||||
color: green
|
||||
tools:
|
||||
- search
|
||||
- playwright-test/browser_click
|
||||
- playwright-test/browser_close
|
||||
- playwright-test/browser_console_messages
|
||||
- playwright-test/browser_drag
|
||||
- playwright-test/browser_evaluate
|
||||
- playwright-test/browser_file_upload
|
||||
- playwright-test/browser_handle_dialog
|
||||
- playwright-test/browser_hover
|
||||
- playwright-test/browser_navigate
|
||||
- playwright-test/browser_navigate_back
|
||||
- playwright-test/browser_network_requests
|
||||
- playwright-test/browser_press_key
|
||||
- playwright-test/browser_run_code
|
||||
- playwright-test/browser_select_option
|
||||
- playwright-test/browser_snapshot
|
||||
- playwright-test/browser_take_screenshot
|
||||
- playwright-test/browser_type
|
||||
- playwright-test/browser_wait_for
|
||||
- playwright-test/planner_setup_page
|
||||
- playwright-test/planner_save_plan
|
||||
---
|
||||
|
||||
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
|
||||
scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
|
||||
planning.
|
||||
|
||||
You will:
|
||||
|
||||
1. **Navigate and Explore**
|
||||
- Invoke the `planner_setup_page` tool once to set up page before using any other tools
|
||||
- Explore the browser snapshot
|
||||
- Do not take screenshots unless absolutely necessary
|
||||
- Use `browser_*` tools to navigate and discover interface
|
||||
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
|
||||
|
||||
2. **Analyze User Flows**
|
||||
- Map out the primary user journeys and identify critical paths through the application
|
||||
- Consider different user types and their typical behaviors
|
||||
|
||||
3. **Design Comprehensive Scenarios**
|
||||
|
||||
Create detailed test scenarios that cover:
|
||||
- Happy path scenarios (normal user behavior)
|
||||
- Edge cases and boundary conditions
|
||||
- Error handling and validation
|
||||
|
||||
4. **Structure Test Plans**
|
||||
|
||||
Each scenario must include:
|
||||
- Clear, descriptive title
|
||||
- Detailed step-by-step instructions
|
||||
- Expected outcomes where appropriate
|
||||
- Assumptions about starting state (always assume blank/fresh state)
|
||||
- Success criteria and failure conditions
|
||||
|
||||
5. **Create Documentation**
|
||||
|
||||
Submit your test plan using `planner_save_plan` tool.
|
||||
|
||||
**Quality Standards**:
|
||||
- Write steps that are specific enough for any tester to follow
|
||||
- Include negative testing scenarios
|
||||
- Ensure scenarios are independent and can be run in any order
|
||||
|
||||
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
|
||||
professional formatting suitable for sharing with development and QA teams.
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var config_exports = {};
|
||||
__export(config_exports, {
|
||||
FullConfigInternal: () => FullConfigInternal,
|
||||
FullProjectInternal: () => FullProjectInternal,
|
||||
builtInReporters: () => builtInReporters,
|
||||
defaultGrep: () => defaultGrep,
|
||||
defaultReporter: () => defaultReporter,
|
||||
defaultTimeout: () => defaultTimeout,
|
||||
getProjectId: () => getProjectId,
|
||||
takeFirst: () => takeFirst,
|
||||
toReporters: () => toReporters
|
||||
});
|
||||
module.exports = __toCommonJS(config_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_os = __toESM(require("os"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_util = require("../util");
|
||||
const defaultTimeout = 3e4;
|
||||
class FullConfigInternal {
|
||||
constructor(location, userConfig, configCLIOverrides, metadata) {
|
||||
this.projects = [];
|
||||
this.cliArgs = [];
|
||||
this.cliListOnly = false;
|
||||
this.loadFileFilters = [];
|
||||
this.preOnlyTestFilters = [];
|
||||
this.postShardTestFilters = [];
|
||||
this.defineConfigWasUsed = false;
|
||||
this.globalSetups = [];
|
||||
this.globalTeardowns = [];
|
||||
if (configCLIOverrides.projects && userConfig.projects)
|
||||
throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
|
||||
const { resolvedConfigFile, configDir } = location;
|
||||
const packageJsonPath = (0, import_util.getPackageJsonPath)(configDir);
|
||||
const packageJsonDir = packageJsonPath ? import_path.default.dirname(packageJsonPath) : process.cwd();
|
||||
this.configDir = configDir;
|
||||
this.configCLIOverrides = configCLIOverrides;
|
||||
const privateConfiguration = userConfig["@playwright/test"];
|
||||
this.plugins = (privateConfiguration?.plugins || []).map((p) => ({ factory: p }));
|
||||
this.singleTSConfigPath = pathResolve(configDir, userConfig.tsconfig);
|
||||
this.captureGitInfo = userConfig.captureGitInfo;
|
||||
this.failOnFlakyTests = takeFirst(configCLIOverrides.failOnFlakyTests, userConfig.failOnFlakyTests, false);
|
||||
this.globalSetups = (Array.isArray(userConfig.globalSetup) ? userConfig.globalSetup : [userConfig.globalSetup]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
|
||||
this.globalTeardowns = (Array.isArray(userConfig.globalTeardown) ? userConfig.globalTeardown : [userConfig.globalTeardown]).map((s) => resolveScript(s, configDir)).filter((script) => script !== void 0);
|
||||
userConfig.metadata = userConfig.metadata || {};
|
||||
const globalTags = Array.isArray(userConfig.tag) ? userConfig.tag : userConfig.tag ? [userConfig.tag] : [];
|
||||
for (const tag of globalTags) {
|
||||
if (tag[0] !== "@")
|
||||
throw new Error(`Tag must start with "@" symbol, got "${tag}" instead.`);
|
||||
}
|
||||
this.config = {
|
||||
configFile: resolvedConfigFile,
|
||||
rootDir: pathResolve(configDir, userConfig.testDir) || configDir,
|
||||
forbidOnly: takeFirst(configCLIOverrides.forbidOnly, userConfig.forbidOnly, false),
|
||||
fullyParallel: takeFirst(configCLIOverrides.fullyParallel, userConfig.fullyParallel, false),
|
||||
globalSetup: this.globalSetups[0] ?? null,
|
||||
globalTeardown: this.globalTeardowns[0] ?? null,
|
||||
globalTimeout: takeFirst(configCLIOverrides.debug ? 0 : void 0, configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
|
||||
grep: takeFirst(userConfig.grep, defaultGrep),
|
||||
grepInvert: takeFirst(userConfig.grepInvert, null),
|
||||
maxFailures: takeFirst(configCLIOverrides.debug ? 1 : void 0, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
|
||||
metadata: metadata ?? userConfig.metadata,
|
||||
preserveOutput: takeFirst(userConfig.preserveOutput, "always"),
|
||||
projects: [],
|
||||
quiet: takeFirst(configCLIOverrides.quiet, userConfig.quiet, false),
|
||||
reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
|
||||
reportSlowTests: takeFirst(userConfig.reportSlowTests, {
|
||||
max: 5,
|
||||
threshold: 3e5
|
||||
/* 5 minutes */
|
||||
}),
|
||||
shard: takeFirst(configCLIOverrides.shard, userConfig.shard, null),
|
||||
tags: globalTags,
|
||||
updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, userConfig.updateSnapshots, "missing"),
|
||||
updateSourceMethod: takeFirst(configCLIOverrides.updateSourceMethod, userConfig.updateSourceMethod, "patch"),
|
||||
version: require("../../package.json").version,
|
||||
workers: resolveWorkers(takeFirst(configCLIOverrides.debug || configCLIOverrides.pause ? 1 : void 0, configCLIOverrides.workers, userConfig.workers, "50%")),
|
||||
webServer: null
|
||||
};
|
||||
for (const key in userConfig) {
|
||||
if (key.startsWith("@"))
|
||||
this.config[key] = userConfig[key];
|
||||
}
|
||||
this.config[configInternalSymbol] = this;
|
||||
const webServers = takeFirst(userConfig.webServer, null);
|
||||
if (Array.isArray(webServers)) {
|
||||
this.config.webServer = null;
|
||||
this.webServers = webServers;
|
||||
} else if (webServers) {
|
||||
this.config.webServer = webServers;
|
||||
this.webServers = [webServers];
|
||||
} else {
|
||||
this.webServers = [];
|
||||
}
|
||||
const projectConfigs = configCLIOverrides.projects || userConfig.projects || [{ ...userConfig, workers: void 0 }];
|
||||
this.projects = projectConfigs.map((p) => new FullProjectInternal(configDir, userConfig, this, p, this.configCLIOverrides, packageJsonDir));
|
||||
resolveProjectDependencies(this.projects);
|
||||
this._assignUniqueProjectIds(this.projects);
|
||||
this.config.projects = this.projects.map((p) => p.project);
|
||||
}
|
||||
_assignUniqueProjectIds(projects) {
|
||||
const usedNames = /* @__PURE__ */ new Set();
|
||||
for (const p of projects) {
|
||||
const name = p.project.name || "";
|
||||
for (let i = 0; i < projects.length; ++i) {
|
||||
const candidate = name + (i ? i : "");
|
||||
if (usedNames.has(candidate))
|
||||
continue;
|
||||
p.id = candidate;
|
||||
p.project.__projectId = p.id;
|
||||
usedNames.add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class FullProjectInternal {
|
||||
constructor(configDir, config, fullConfig, projectConfig, configCLIOverrides, packageJsonDir) {
|
||||
this.id = "";
|
||||
this.deps = [];
|
||||
this.fullConfig = fullConfig;
|
||||
const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir);
|
||||
this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate);
|
||||
this.project = {
|
||||
grep: takeFirst(projectConfig.grep, config.grep, defaultGrep),
|
||||
grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null),
|
||||
outputDir: takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, projectConfig.outputDir), pathResolve(configDir, config.outputDir), import_path.default.join(packageJsonDir, "test-results")),
|
||||
// Note: we either apply the cli override for repeatEach or not, depending on whether the
|
||||
// project is top-level vs dependency. See collectProjectsAndTestFiles in loadUtils.
|
||||
repeatEach: takeFirst(projectConfig.repeatEach, config.repeatEach, 1),
|
||||
retries: takeFirst(configCLIOverrides.retries, projectConfig.retries, config.retries, 0),
|
||||
metadata: takeFirst(projectConfig.metadata, config.metadata, {}),
|
||||
name: takeFirst(projectConfig.name, config.name, ""),
|
||||
testDir,
|
||||
snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
|
||||
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
|
||||
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, "**/*.@(spec|test).?(c|m)[jt]s?(x)"),
|
||||
timeout: takeFirst(configCLIOverrides.debug === "inspector" ? 0 : void 0, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
|
||||
use: (0, import_util.mergeObjects)(config.use, projectConfig.use, configCLIOverrides.use),
|
||||
dependencies: projectConfig.dependencies || [],
|
||||
teardown: projectConfig.teardown,
|
||||
ignoreSnapshots: takeFirst(configCLIOverrides.ignoreSnapshots, projectConfig.ignoreSnapshots, config.ignoreSnapshots, false)
|
||||
};
|
||||
this.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, void 0);
|
||||
this.expect = takeFirst(projectConfig.expect, config.expect, {});
|
||||
if (this.expect.toHaveScreenshot?.stylePath) {
|
||||
const stylePaths = Array.isArray(this.expect.toHaveScreenshot.stylePath) ? this.expect.toHaveScreenshot.stylePath : [this.expect.toHaveScreenshot.stylePath];
|
||||
this.expect.toHaveScreenshot.stylePath = stylePaths.map((stylePath) => import_path.default.resolve(configDir, stylePath));
|
||||
}
|
||||
this.respectGitIgnore = takeFirst(projectConfig.respectGitIgnore, config.respectGitIgnore, !projectConfig.testDir && !config.testDir);
|
||||
this.workers = projectConfig.workers ? resolveWorkers(projectConfig.workers) : void 0;
|
||||
if (configCLIOverrides.debug && this.workers)
|
||||
this.workers = 1;
|
||||
}
|
||||
}
|
||||
function takeFirst(...args) {
|
||||
for (const arg of args) {
|
||||
if (arg !== void 0)
|
||||
return arg;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
function pathResolve(baseDir, relative) {
|
||||
if (!relative)
|
||||
return void 0;
|
||||
return import_path.default.resolve(baseDir, relative);
|
||||
}
|
||||
function resolveReporters(reporters, rootDir) {
|
||||
return toReporters(reporters)?.map(([id, arg]) => {
|
||||
if (builtInReporters.includes(id))
|
||||
return [id, arg];
|
||||
return [require.resolve(id, { paths: [rootDir] }), arg];
|
||||
});
|
||||
}
|
||||
function resolveWorkers(workers) {
|
||||
if (typeof workers === "string") {
|
||||
if (workers.endsWith("%")) {
|
||||
const cpus = import_os.default.cpus().length;
|
||||
return Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
|
||||
}
|
||||
const parsedWorkers = parseInt(workers, 10);
|
||||
if (isNaN(parsedWorkers))
|
||||
throw new Error(`Workers ${workers} must be a number or percentage.`);
|
||||
return parsedWorkers;
|
||||
}
|
||||
return workers;
|
||||
}
|
||||
function resolveProjectDependencies(projects) {
|
||||
const teardownSet = /* @__PURE__ */ new Set();
|
||||
for (const project of projects) {
|
||||
for (const dependencyName of project.project.dependencies) {
|
||||
const dependencies = projects.filter((p) => p.project.name === dependencyName);
|
||||
if (!dependencies.length)
|
||||
throw new Error(`Project '${project.project.name}' depends on unknown project '${dependencyName}'`);
|
||||
if (dependencies.length > 1)
|
||||
throw new Error(`Project dependencies should have unique names, reading ${dependencyName}`);
|
||||
project.deps.push(...dependencies);
|
||||
}
|
||||
if (project.project.teardown) {
|
||||
const teardowns = projects.filter((p) => p.project.name === project.project.teardown);
|
||||
if (!teardowns.length)
|
||||
throw new Error(`Project '${project.project.name}' has unknown teardown project '${project.project.teardown}'`);
|
||||
if (teardowns.length > 1)
|
||||
throw new Error(`Project teardowns should have unique names, reading ${project.project.teardown}`);
|
||||
const teardown = teardowns[0];
|
||||
project.teardown = teardown;
|
||||
teardownSet.add(teardown);
|
||||
}
|
||||
}
|
||||
for (const teardown of teardownSet) {
|
||||
if (teardown.deps.length)
|
||||
throw new Error(`Teardown project ${teardown.project.name} must not have dependencies`);
|
||||
}
|
||||
for (const project of projects) {
|
||||
for (const dep of project.deps) {
|
||||
if (teardownSet.has(dep))
|
||||
throw new Error(`Project ${project.project.name} must not depend on a teardown project ${dep.project.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
function toReporters(reporters) {
|
||||
if (!reporters)
|
||||
return;
|
||||
if (typeof reporters === "string")
|
||||
return [[reporters]];
|
||||
return reporters;
|
||||
}
|
||||
const builtInReporters = ["list", "line", "dot", "json", "junit", "null", "github", "html", "blob"];
|
||||
function resolveScript(id, rootDir) {
|
||||
if (!id)
|
||||
return void 0;
|
||||
const localPath = import_path.default.resolve(rootDir, id);
|
||||
if (import_fs.default.existsSync(localPath))
|
||||
return localPath;
|
||||
return require.resolve(id, { paths: [rootDir] });
|
||||
}
|
||||
const defaultGrep = /.*/;
|
||||
const defaultReporter = process.env.CI ? "dot" : "list";
|
||||
const configInternalSymbol = Symbol("configInternalSymbol");
|
||||
function getProjectId(project) {
|
||||
return project.__projectId;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
FullConfigInternal,
|
||||
FullProjectInternal,
|
||||
builtInReporters,
|
||||
defaultGrep,
|
||||
defaultReporter,
|
||||
defaultTimeout,
|
||||
getProjectId,
|
||||
takeFirst,
|
||||
toReporters
|
||||
});
|
||||
+344
@@ -0,0 +1,344 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var configLoader_exports = {};
|
||||
__export(configLoader_exports, {
|
||||
defineConfig: () => defineConfig,
|
||||
deserializeConfig: () => deserializeConfig,
|
||||
loadConfig: () => loadConfig,
|
||||
loadConfigFromFile: () => loadConfigFromFile,
|
||||
loadEmptyConfigForMergeReports: () => loadEmptyConfigForMergeReports,
|
||||
resolveConfigLocation: () => resolveConfigLocation
|
||||
});
|
||||
module.exports = __toCommonJS(configLoader_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_transform = require("../transform/transform");
|
||||
var import_util = require("../util");
|
||||
var import_config = require("./config");
|
||||
var import_esmLoaderHost = require("./esmLoaderHost");
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
const kDefineConfigWasUsed = Symbol("defineConfigWasUsed");
|
||||
const defineConfig = (...configs) => {
|
||||
let result = configs[0];
|
||||
for (let i = 1; i < configs.length; ++i) {
|
||||
const config = configs[i];
|
||||
const prevProjects = result.projects;
|
||||
result = {
|
||||
...result,
|
||||
...config,
|
||||
expect: {
|
||||
...result.expect,
|
||||
...config.expect
|
||||
},
|
||||
use: {
|
||||
...result.use,
|
||||
...config.use
|
||||
},
|
||||
build: {
|
||||
...result.build,
|
||||
...config.build
|
||||
},
|
||||
webServer: [
|
||||
...Array.isArray(result.webServer) ? result.webServer : result.webServer ? [result.webServer] : [],
|
||||
...Array.isArray(config.webServer) ? config.webServer : config.webServer ? [config.webServer] : []
|
||||
]
|
||||
};
|
||||
if (!result.projects && !config.projects)
|
||||
continue;
|
||||
const projectOverrides = /* @__PURE__ */ new Map();
|
||||
for (const project of config.projects || [])
|
||||
projectOverrides.set(project.name, project);
|
||||
const projects = [];
|
||||
for (const project of prevProjects || []) {
|
||||
const projectOverride = projectOverrides.get(project.name);
|
||||
if (projectOverride) {
|
||||
projects.push({
|
||||
...project,
|
||||
...projectOverride,
|
||||
use: {
|
||||
...project.use,
|
||||
...projectOverride.use
|
||||
}
|
||||
});
|
||||
projectOverrides.delete(project.name);
|
||||
} else {
|
||||
projects.push(project);
|
||||
}
|
||||
}
|
||||
projects.push(...projectOverrides.values());
|
||||
result.projects = projects;
|
||||
}
|
||||
result[kDefineConfigWasUsed] = true;
|
||||
return result;
|
||||
};
|
||||
async function deserializeConfig(data) {
|
||||
if (data.compilationCache)
|
||||
(0, import_compilationCache.addToCompilationCache)(data.compilationCache);
|
||||
return await loadConfig(data.location, data.configCLIOverrides, void 0, data.metadata ? JSON.parse(data.metadata) : void 0);
|
||||
}
|
||||
async function loadUserConfig(location) {
|
||||
let object = location.resolvedConfigFile ? await (0, import_transform.requireOrImport)(location.resolvedConfigFile) : {};
|
||||
if (object && typeof object === "object" && "default" in object)
|
||||
object = object["default"];
|
||||
return object;
|
||||
}
|
||||
async function loadConfig(location, overrides, ignoreProjectDependencies = false, metadata) {
|
||||
if (!(0, import_esmLoaderHost.registerESMLoader)()) {
|
||||
if (location.resolvedConfigFile && (0, import_util.fileIsModule)(location.resolvedConfigFile))
|
||||
throw (0, import_util.errorWithFile)(location.resolvedConfigFile, `Playwright requires Node.js 18.19 or higher to load esm modules. Please update your version of Node.js.`);
|
||||
}
|
||||
(0, import_transform.setSingleTSConfig)(overrides?.tsconfig);
|
||||
await (0, import_esmLoaderHost.configureESMLoader)();
|
||||
const userConfig = await loadUserConfig(location);
|
||||
validateConfig(location.resolvedConfigFile || "<default config>", userConfig);
|
||||
const fullConfig = new import_config.FullConfigInternal(location, userConfig, overrides || {}, metadata);
|
||||
fullConfig.defineConfigWasUsed = !!userConfig[kDefineConfigWasUsed];
|
||||
if (ignoreProjectDependencies) {
|
||||
for (const project of fullConfig.projects) {
|
||||
project.deps = [];
|
||||
project.teardown = void 0;
|
||||
}
|
||||
}
|
||||
const babelPlugins = userConfig["@playwright/test"]?.babelPlugins || [];
|
||||
const external = userConfig.build?.external || [];
|
||||
(0, import_transform.setTransformConfig)({ babelPlugins, external });
|
||||
if (!overrides?.tsconfig)
|
||||
(0, import_transform.setSingleTSConfig)(fullConfig?.singleTSConfigPath);
|
||||
await (0, import_esmLoaderHost.configureESMLoaderTransformConfig)();
|
||||
return fullConfig;
|
||||
}
|
||||
function validateConfig(file, config) {
|
||||
if (typeof config !== "object" || !config)
|
||||
throw (0, import_util.errorWithFile)(file, `Configuration file must export a single object`);
|
||||
validateProject(file, config, "config");
|
||||
if ("forbidOnly" in config && config.forbidOnly !== void 0) {
|
||||
if (typeof config.forbidOnly !== "boolean")
|
||||
throw (0, import_util.errorWithFile)(file, `config.forbidOnly must be a boolean`);
|
||||
}
|
||||
if ("globalSetup" in config && config.globalSetup !== void 0) {
|
||||
if (Array.isArray(config.globalSetup)) {
|
||||
config.globalSetup.forEach((item, index) => {
|
||||
if (typeof item !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `config.globalSetup[${index}] must be a string`);
|
||||
});
|
||||
} else if (typeof config.globalSetup !== "string") {
|
||||
throw (0, import_util.errorWithFile)(file, `config.globalSetup must be a string`);
|
||||
}
|
||||
}
|
||||
if ("globalTeardown" in config && config.globalTeardown !== void 0) {
|
||||
if (Array.isArray(config.globalTeardown)) {
|
||||
config.globalTeardown.forEach((item, index) => {
|
||||
if (typeof item !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `config.globalTeardown[${index}] must be a string`);
|
||||
});
|
||||
} else if (typeof config.globalTeardown !== "string") {
|
||||
throw (0, import_util.errorWithFile)(file, `config.globalTeardown must be a string`);
|
||||
}
|
||||
}
|
||||
if ("globalTimeout" in config && config.globalTimeout !== void 0) {
|
||||
if (typeof config.globalTimeout !== "number" || config.globalTimeout < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `config.globalTimeout must be a non-negative number`);
|
||||
}
|
||||
if ("grep" in config && config.grep !== void 0) {
|
||||
if (Array.isArray(config.grep)) {
|
||||
config.grep.forEach((item, index) => {
|
||||
if (!(0, import_utils.isRegExp)(item))
|
||||
throw (0, import_util.errorWithFile)(file, `config.grep[${index}] must be a RegExp`);
|
||||
});
|
||||
} else if (!(0, import_utils.isRegExp)(config.grep)) {
|
||||
throw (0, import_util.errorWithFile)(file, `config.grep must be a RegExp`);
|
||||
}
|
||||
}
|
||||
if ("grepInvert" in config && config.grepInvert !== void 0) {
|
||||
if (Array.isArray(config.grepInvert)) {
|
||||
config.grepInvert.forEach((item, index) => {
|
||||
if (!(0, import_utils.isRegExp)(item))
|
||||
throw (0, import_util.errorWithFile)(file, `config.grepInvert[${index}] must be a RegExp`);
|
||||
});
|
||||
} else if (!(0, import_utils.isRegExp)(config.grepInvert)) {
|
||||
throw (0, import_util.errorWithFile)(file, `config.grepInvert must be a RegExp`);
|
||||
}
|
||||
}
|
||||
if ("maxFailures" in config && config.maxFailures !== void 0) {
|
||||
if (typeof config.maxFailures !== "number" || config.maxFailures < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `config.maxFailures must be a non-negative number`);
|
||||
}
|
||||
if ("preserveOutput" in config && config.preserveOutput !== void 0) {
|
||||
if (typeof config.preserveOutput !== "string" || !["always", "never", "failures-only"].includes(config.preserveOutput))
|
||||
throw (0, import_util.errorWithFile)(file, `config.preserveOutput must be one of "always", "never" or "failures-only"`);
|
||||
}
|
||||
if ("projects" in config && config.projects !== void 0) {
|
||||
if (!Array.isArray(config.projects))
|
||||
throw (0, import_util.errorWithFile)(file, `config.projects must be an array`);
|
||||
config.projects.forEach((project, index) => {
|
||||
validateProject(file, project, `config.projects[${index}]`);
|
||||
});
|
||||
}
|
||||
if ("quiet" in config && config.quiet !== void 0) {
|
||||
if (typeof config.quiet !== "boolean")
|
||||
throw (0, import_util.errorWithFile)(file, `config.quiet must be a boolean`);
|
||||
}
|
||||
if ("reporter" in config && config.reporter !== void 0) {
|
||||
if (Array.isArray(config.reporter)) {
|
||||
config.reporter.forEach((item, index) => {
|
||||
if (!Array.isArray(item) || item.length <= 0 || item.length > 2 || typeof item[0] !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `config.reporter[${index}] must be a tuple [name, optionalArgument]`);
|
||||
});
|
||||
} else if (typeof config.reporter !== "string") {
|
||||
throw (0, import_util.errorWithFile)(file, `config.reporter must be a string`);
|
||||
}
|
||||
}
|
||||
if ("reportSlowTests" in config && config.reportSlowTests !== void 0 && config.reportSlowTests !== null) {
|
||||
if (!config.reportSlowTests || typeof config.reportSlowTests !== "object")
|
||||
throw (0, import_util.errorWithFile)(file, `config.reportSlowTests must be an object`);
|
||||
if (!("max" in config.reportSlowTests) || typeof config.reportSlowTests.max !== "number" || config.reportSlowTests.max < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `config.reportSlowTests.max must be a non-negative number`);
|
||||
if (!("threshold" in config.reportSlowTests) || typeof config.reportSlowTests.threshold !== "number" || config.reportSlowTests.threshold < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `config.reportSlowTests.threshold must be a non-negative number`);
|
||||
}
|
||||
if ("shard" in config && config.shard !== void 0 && config.shard !== null) {
|
||||
if (!config.shard || typeof config.shard !== "object")
|
||||
throw (0, import_util.errorWithFile)(file, `config.shard must be an object`);
|
||||
if (!("total" in config.shard) || typeof config.shard.total !== "number" || config.shard.total < 1)
|
||||
throw (0, import_util.errorWithFile)(file, `config.shard.total must be a positive number`);
|
||||
if (!("current" in config.shard) || typeof config.shard.current !== "number" || config.shard.current < 1 || config.shard.current > config.shard.total)
|
||||
throw (0, import_util.errorWithFile)(file, `config.shard.current must be a positive number, not greater than config.shard.total`);
|
||||
}
|
||||
if ("updateSnapshots" in config && config.updateSnapshots !== void 0) {
|
||||
if (typeof config.updateSnapshots !== "string" || !["all", "changed", "missing", "none"].includes(config.updateSnapshots))
|
||||
throw (0, import_util.errorWithFile)(file, `config.updateSnapshots must be one of "all", "changed", "missing" or "none"`);
|
||||
}
|
||||
if ("tsconfig" in config && config.tsconfig !== void 0) {
|
||||
if (typeof config.tsconfig !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `config.tsconfig must be a string`);
|
||||
if (!import_fs.default.existsSync(import_path.default.resolve(file, "..", config.tsconfig)))
|
||||
throw (0, import_util.errorWithFile)(file, `config.tsconfig does not exist`);
|
||||
}
|
||||
}
|
||||
function validateProject(file, project, title) {
|
||||
if (typeof project !== "object" || !project)
|
||||
throw (0, import_util.errorWithFile)(file, `${title} must be an object`);
|
||||
if ("name" in project && project.name !== void 0) {
|
||||
if (typeof project.name !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.name must be a string`);
|
||||
}
|
||||
if ("outputDir" in project && project.outputDir !== void 0) {
|
||||
if (typeof project.outputDir !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.outputDir must be a string`);
|
||||
}
|
||||
if ("repeatEach" in project && project.repeatEach !== void 0) {
|
||||
if (typeof project.repeatEach !== "number" || project.repeatEach < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.repeatEach must be a non-negative number`);
|
||||
}
|
||||
if ("retries" in project && project.retries !== void 0) {
|
||||
if (typeof project.retries !== "number" || project.retries < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.retries must be a non-negative number`);
|
||||
}
|
||||
if ("testDir" in project && project.testDir !== void 0) {
|
||||
if (typeof project.testDir !== "string")
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.testDir must be a string`);
|
||||
}
|
||||
for (const prop of ["testIgnore", "testMatch"]) {
|
||||
if (prop in project && project[prop] !== void 0) {
|
||||
const value = project[prop];
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (typeof item !== "string" && !(0, import_utils.isRegExp)(item))
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.${prop}[${index}] must be a string or a RegExp`);
|
||||
});
|
||||
} else if (typeof value !== "string" && !(0, import_utils.isRegExp)(value)) {
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.${prop} must be a string or a RegExp`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ("timeout" in project && project.timeout !== void 0) {
|
||||
if (typeof project.timeout !== "number" || project.timeout < 0)
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.timeout must be a non-negative number`);
|
||||
}
|
||||
if ("use" in project && project.use !== void 0) {
|
||||
if (!project.use || typeof project.use !== "object")
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.use must be an object`);
|
||||
}
|
||||
if ("ignoreSnapshots" in project && project.ignoreSnapshots !== void 0) {
|
||||
if (typeof project.ignoreSnapshots !== "boolean")
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.ignoreSnapshots must be a boolean`);
|
||||
}
|
||||
if ("workers" in project && project.workers !== void 0) {
|
||||
if (typeof project.workers === "number" && project.workers <= 0)
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.workers must be a positive number`);
|
||||
else if (typeof project.workers === "string" && !project.workers.endsWith("%"))
|
||||
throw (0, import_util.errorWithFile)(file, `${title}.workers must be a number or percentage`);
|
||||
}
|
||||
}
|
||||
function resolveConfigLocation(configFile) {
|
||||
const configFileOrDirectory = configFile ? import_path.default.resolve(process.cwd(), configFile) : process.cwd();
|
||||
const resolvedConfigFile = resolveConfigFile(configFileOrDirectory);
|
||||
return {
|
||||
resolvedConfigFile,
|
||||
configDir: resolvedConfigFile ? import_path.default.dirname(resolvedConfigFile) : configFileOrDirectory
|
||||
};
|
||||
}
|
||||
function resolveConfigFile(configFileOrDirectory) {
|
||||
const resolveConfig = (configFile) => {
|
||||
if (import_fs.default.existsSync(configFile))
|
||||
return configFile;
|
||||
};
|
||||
const resolveConfigFileFromDirectory = (directory) => {
|
||||
for (const ext of [".ts", ".js", ".mts", ".mjs", ".cts", ".cjs"]) {
|
||||
const configFile = resolveConfig(import_path.default.resolve(directory, "playwright.config" + ext));
|
||||
if (configFile)
|
||||
return configFile;
|
||||
}
|
||||
};
|
||||
if (!import_fs.default.existsSync(configFileOrDirectory))
|
||||
throw new Error(`${configFileOrDirectory} does not exist`);
|
||||
if (import_fs.default.statSync(configFileOrDirectory).isDirectory()) {
|
||||
const configFile = resolveConfigFileFromDirectory(configFileOrDirectory);
|
||||
if (configFile)
|
||||
return configFile;
|
||||
return void 0;
|
||||
}
|
||||
return configFileOrDirectory;
|
||||
}
|
||||
async function loadConfigFromFile(configFile, overrides, ignoreDeps) {
|
||||
return await loadConfig(resolveConfigLocation(configFile), overrides, ignoreDeps);
|
||||
}
|
||||
async function loadEmptyConfigForMergeReports() {
|
||||
return await loadConfig({ configDir: process.cwd() });
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
defineConfig,
|
||||
deserializeConfig,
|
||||
loadConfig,
|
||||
loadConfigFromFile,
|
||||
loadEmptyConfigForMergeReports,
|
||||
resolveConfigLocation
|
||||
});
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var esmLoaderHost_exports = {};
|
||||
__export(esmLoaderHost_exports, {
|
||||
configureESMLoader: () => configureESMLoader,
|
||||
configureESMLoaderTransformConfig: () => configureESMLoaderTransformConfig,
|
||||
incorporateCompilationCache: () => incorporateCompilationCache,
|
||||
registerESMLoader: () => registerESMLoader,
|
||||
startCollectingFileDeps: () => startCollectingFileDeps,
|
||||
stopCollectingFileDeps: () => stopCollectingFileDeps
|
||||
});
|
||||
module.exports = __toCommonJS(esmLoaderHost_exports);
|
||||
var import_url = __toESM(require("url"));
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
var import_portTransport = require("../transform/portTransport");
|
||||
var import_transform = require("../transform/transform");
|
||||
let loaderChannel;
|
||||
function registerESMLoader() {
|
||||
if (process.env.PW_DISABLE_TS_ESM)
|
||||
return true;
|
||||
if ("Bun" in globalThis)
|
||||
return true;
|
||||
if (loaderChannel)
|
||||
return true;
|
||||
const register = require("node:module").register;
|
||||
if (!register)
|
||||
return false;
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
register(import_url.default.pathToFileURL(require.resolve("../transform/esmLoader")), {
|
||||
data: { port: port2 },
|
||||
transferList: [port2]
|
||||
});
|
||||
loaderChannel = createPortTransport(port1);
|
||||
return true;
|
||||
}
|
||||
function createPortTransport(port) {
|
||||
return new import_portTransport.PortTransport(port, async (method, params) => {
|
||||
if (method === "pushToCompilationCache")
|
||||
(0, import_compilationCache.addToCompilationCache)(params.cache);
|
||||
});
|
||||
}
|
||||
async function startCollectingFileDeps() {
|
||||
if (!loaderChannel)
|
||||
return;
|
||||
await loaderChannel.send("startCollectingFileDeps", {});
|
||||
}
|
||||
async function stopCollectingFileDeps(file) {
|
||||
if (!loaderChannel)
|
||||
return;
|
||||
await loaderChannel.send("stopCollectingFileDeps", { file });
|
||||
}
|
||||
async function incorporateCompilationCache() {
|
||||
if (!loaderChannel)
|
||||
return;
|
||||
const result = await loaderChannel.send("getCompilationCache", {});
|
||||
(0, import_compilationCache.addToCompilationCache)(result.cache);
|
||||
}
|
||||
async function configureESMLoader() {
|
||||
if (!loaderChannel)
|
||||
return;
|
||||
await loaderChannel.send("setSingleTSConfig", { tsconfig: (0, import_transform.singleTSConfig)() });
|
||||
await loaderChannel.send("addToCompilationCache", { cache: (0, import_compilationCache.serializeCompilationCache)() });
|
||||
}
|
||||
async function configureESMLoaderTransformConfig() {
|
||||
if (!loaderChannel)
|
||||
return;
|
||||
await loaderChannel.send("setSingleTSConfig", { tsconfig: (0, import_transform.singleTSConfig)() });
|
||||
await loaderChannel.send("setTransformConfig", { config: (0, import_transform.transformConfig)() });
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
configureESMLoader,
|
||||
configureESMLoaderTransformConfig,
|
||||
incorporateCompilationCache,
|
||||
registerESMLoader,
|
||||
startCollectingFileDeps,
|
||||
stopCollectingFileDeps
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var expectBundle_exports = {};
|
||||
__export(expectBundle_exports, {
|
||||
expect: () => expect
|
||||
});
|
||||
module.exports = __toCommonJS(expectBundle_exports);
|
||||
const expect = require("./expectBundleImpl").expect;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
expect
|
||||
});
|
||||
+407
File diff suppressed because one or more lines are too long
+302
@@ -0,0 +1,302 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var fixtures_exports = {};
|
||||
__export(fixtures_exports, {
|
||||
FixturePool: () => FixturePool,
|
||||
fixtureParameterNames: () => fixtureParameterNames,
|
||||
formatPotentiallyInternalLocation: () => formatPotentiallyInternalLocation,
|
||||
inheritFixtureNames: () => inheritFixtureNames
|
||||
});
|
||||
module.exports = __toCommonJS(fixtures_exports);
|
||||
var import_crypto = __toESM(require("crypto"));
|
||||
var import_util = require("../util");
|
||||
const kScopeOrder = ["test", "worker"];
|
||||
function isFixtureTuple(value) {
|
||||
return Array.isArray(value) && typeof value[1] === "object";
|
||||
}
|
||||
function isFixtureOption(value) {
|
||||
return isFixtureTuple(value) && !!value[1].option;
|
||||
}
|
||||
class FixturePool {
|
||||
constructor(fixturesList, onLoadError, parentPool, disallowWorkerFixtures, optionOverrides) {
|
||||
this._registrations = new Map(parentPool ? parentPool._registrations : []);
|
||||
this._onLoadError = onLoadError;
|
||||
const allOverrides = optionOverrides?.overrides ?? {};
|
||||
const overrideKeys = new Set(Object.keys(allOverrides));
|
||||
for (const list of fixturesList) {
|
||||
this._appendFixtureList(list, !!disallowWorkerFixtures, false);
|
||||
const selectedOverrides = {};
|
||||
for (const [key, value] of Object.entries(list.fixtures)) {
|
||||
if (isFixtureOption(value) && overrideKeys.has(key))
|
||||
selectedOverrides[key] = [allOverrides[key], value[1]];
|
||||
}
|
||||
if (Object.entries(selectedOverrides).length)
|
||||
this._appendFixtureList({ fixtures: selectedOverrides, location: optionOverrides.location }, !!disallowWorkerFixtures, true);
|
||||
}
|
||||
this.digest = this.validate();
|
||||
}
|
||||
_appendFixtureList(list, disallowWorkerFixtures, isOptionsOverride) {
|
||||
const { fixtures, location } = list;
|
||||
for (const entry of Object.entries(fixtures)) {
|
||||
const name = entry[0];
|
||||
let value = entry[1];
|
||||
let options;
|
||||
if (isFixtureTuple(value)) {
|
||||
options = {
|
||||
auto: value[1].auto ?? false,
|
||||
scope: value[1].scope || "test",
|
||||
option: !!value[1].option,
|
||||
timeout: value[1].timeout,
|
||||
customTitle: value[1].title,
|
||||
box: value[1].box
|
||||
};
|
||||
value = value[0];
|
||||
}
|
||||
let fn = value;
|
||||
const previous = this._registrations.get(name);
|
||||
if (previous && options) {
|
||||
if (previous.scope !== options.scope) {
|
||||
this._addLoadError(`Fixture "${name}" has already been registered as a { scope: '${previous.scope}' } fixture defined in ${(0, import_util.formatLocation)(previous.location)}.`, location);
|
||||
continue;
|
||||
}
|
||||
if (previous.auto !== options.auto) {
|
||||
this._addLoadError(`Fixture "${name}" has already been registered as a { auto: '${previous.scope}' } fixture defined in ${(0, import_util.formatLocation)(previous.location)}.`, location);
|
||||
continue;
|
||||
}
|
||||
} else if (previous) {
|
||||
options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle };
|
||||
} else if (!options) {
|
||||
options = { auto: false, scope: "test", option: false, timeout: void 0 };
|
||||
}
|
||||
if (!kScopeOrder.includes(options.scope)) {
|
||||
this._addLoadError(`Fixture "${name}" has unknown { scope: '${options.scope}' }.`, location);
|
||||
continue;
|
||||
}
|
||||
if (options.scope === "worker" && disallowWorkerFixtures) {
|
||||
this._addLoadError(`Cannot use({ ${name} }) in a describe group, because it forces a new worker.
|
||||
Make it top-level in the test file or put in the configuration file.`, location);
|
||||
continue;
|
||||
}
|
||||
if (fn === void 0 && options.option && previous) {
|
||||
let original = previous;
|
||||
while (!original.optionOverride && original.super)
|
||||
original = original.super;
|
||||
fn = original.fn;
|
||||
}
|
||||
const deps = fixtureParameterNames(fn, location, (e) => this._onLoadError(e));
|
||||
const registration = { id: "", name, location, scope: options.scope, fn, auto: options.auto, option: options.option, timeout: options.timeout, customTitle: options.customTitle, box: options.box, deps, super: previous, optionOverride: isOptionsOverride };
|
||||
registrationId(registration);
|
||||
this._registrations.set(name, registration);
|
||||
}
|
||||
}
|
||||
validate() {
|
||||
const markers = /* @__PURE__ */ new Map();
|
||||
const stack = [];
|
||||
let hasDependencyErrors = false;
|
||||
const addDependencyError = (message, location) => {
|
||||
hasDependencyErrors = true;
|
||||
this._addLoadError(message, location);
|
||||
};
|
||||
const visit = (registration, boxedOnly) => {
|
||||
markers.set(registration, "visiting");
|
||||
stack.push(registration);
|
||||
for (const name of registration.deps) {
|
||||
const dep = this.resolve(name, registration);
|
||||
if (!dep) {
|
||||
if (name === registration.name)
|
||||
addDependencyError(`Fixture "${registration.name}" references itself, but does not have a base implementation.`, registration.location);
|
||||
else
|
||||
addDependencyError(`Fixture "${registration.name}" has unknown parameter "${name}".`, registration.location);
|
||||
continue;
|
||||
}
|
||||
if (kScopeOrder.indexOf(registration.scope) > kScopeOrder.indexOf(dep.scope)) {
|
||||
addDependencyError(`${registration.scope} fixture "${registration.name}" cannot depend on a ${dep.scope} fixture "${name}" defined in ${formatPotentiallyInternalLocation(dep.location)}.`, registration.location);
|
||||
continue;
|
||||
}
|
||||
if (!markers.has(dep)) {
|
||||
visit(dep, boxedOnly);
|
||||
} else if (markers.get(dep) === "visiting") {
|
||||
const index = stack.indexOf(dep);
|
||||
const allRegs = stack.slice(index, stack.length);
|
||||
const filteredRegs = allRegs.filter((r) => !r.box);
|
||||
const regs = boxedOnly ? filteredRegs : allRegs;
|
||||
const names2 = regs.map((r) => `"${r.name}"`);
|
||||
addDependencyError(`Fixtures ${names2.join(" -> ")} -> "${dep.name}" form a dependency cycle: ${regs.map((r) => formatPotentiallyInternalLocation(r.location)).join(" -> ")} -> ${formatPotentiallyInternalLocation(dep.location)}`, dep.location);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
markers.set(registration, "visited");
|
||||
stack.pop();
|
||||
};
|
||||
const names = Array.from(this._registrations.keys()).sort();
|
||||
for (const name of names) {
|
||||
const registration = this._registrations.get(name);
|
||||
if (!registration.box)
|
||||
visit(registration, true);
|
||||
}
|
||||
if (!hasDependencyErrors) {
|
||||
for (const name of names) {
|
||||
const registration = this._registrations.get(name);
|
||||
if (registration.box)
|
||||
visit(registration, false);
|
||||
}
|
||||
}
|
||||
const hash = import_crypto.default.createHash("sha1");
|
||||
for (const name of names) {
|
||||
const registration = this._registrations.get(name);
|
||||
if (registration.scope === "worker")
|
||||
hash.update(registration.id + ";");
|
||||
}
|
||||
return hash.digest("hex");
|
||||
}
|
||||
validateFunction(fn, prefix, location) {
|
||||
for (const name of fixtureParameterNames(fn, location, (e) => this._onLoadError(e))) {
|
||||
const registration = this._registrations.get(name);
|
||||
if (!registration)
|
||||
this._addLoadError(`${prefix} has unknown parameter "${name}".`, location);
|
||||
}
|
||||
}
|
||||
resolve(name, forFixture) {
|
||||
if (name === forFixture?.name)
|
||||
return forFixture.super;
|
||||
return this._registrations.get(name);
|
||||
}
|
||||
autoFixtures() {
|
||||
return [...this._registrations.values()].filter((r) => r.auto !== false);
|
||||
}
|
||||
_addLoadError(message, location) {
|
||||
this._onLoadError({ message, location });
|
||||
}
|
||||
}
|
||||
const signatureSymbol = Symbol("signature");
|
||||
function formatPotentiallyInternalLocation(location) {
|
||||
const isUserFixture = location && (0, import_util.filterStackFile)(location.file);
|
||||
return isUserFixture ? (0, import_util.formatLocation)(location) : "<builtin>";
|
||||
}
|
||||
function fixtureParameterNames(fn, location, onError) {
|
||||
if (typeof fn !== "function")
|
||||
return [];
|
||||
if (!fn[signatureSymbol])
|
||||
fn[signatureSymbol] = innerFixtureParameterNames(fn, location, onError);
|
||||
return fn[signatureSymbol];
|
||||
}
|
||||
function inheritFixtureNames(from, to) {
|
||||
to[signatureSymbol] = from[signatureSymbol];
|
||||
}
|
||||
function innerFixtureParameterNames(fn, location, onError) {
|
||||
const text = filterOutComments(fn.toString());
|
||||
const match = text.match(/(?:async)?(?:\s+function)?[^(]*\(([^)]*)/);
|
||||
if (!match)
|
||||
return [];
|
||||
const trimmedParams = match[1].trim();
|
||||
if (!trimmedParams)
|
||||
return [];
|
||||
const [firstParam] = splitByComma(trimmedParams);
|
||||
if (firstParam[0] !== "{" || firstParam[firstParam.length - 1] !== "}") {
|
||||
onError({ message: "First argument must use the object destructuring pattern: " + firstParam, location });
|
||||
return [];
|
||||
}
|
||||
const props = splitByComma(firstParam.substring(1, firstParam.length - 1)).map((prop) => {
|
||||
const colon = prop.indexOf(":");
|
||||
return colon === -1 ? prop.trim() : prop.substring(0, colon).trim();
|
||||
});
|
||||
const restProperty = props.find((prop) => prop.startsWith("..."));
|
||||
if (restProperty) {
|
||||
onError({ message: `Rest property "${restProperty}" is not supported. List all used fixtures explicitly, separated by comma.`, location });
|
||||
return [];
|
||||
}
|
||||
return props;
|
||||
}
|
||||
function filterOutComments(s) {
|
||||
const result = [];
|
||||
let commentState = "none";
|
||||
for (let i = 0; i < s.length; ++i) {
|
||||
if (commentState === "singleline") {
|
||||
if (s[i] === "\n")
|
||||
commentState = "none";
|
||||
} else if (commentState === "multiline") {
|
||||
if (s[i - 1] === "*" && s[i] === "/")
|
||||
commentState = "none";
|
||||
} else if (commentState === "none") {
|
||||
if (s[i] === "/" && s[i + 1] === "/") {
|
||||
commentState = "singleline";
|
||||
} else if (s[i] === "/" && s[i + 1] === "*") {
|
||||
commentState = "multiline";
|
||||
i += 2;
|
||||
} else {
|
||||
result.push(s[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.join("");
|
||||
}
|
||||
function splitByComma(s) {
|
||||
const result = [];
|
||||
const stack = [];
|
||||
let start = 0;
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
if (s[i] === "{" || s[i] === "[") {
|
||||
stack.push(s[i] === "{" ? "}" : "]");
|
||||
} else if (s[i] === stack[stack.length - 1]) {
|
||||
stack.pop();
|
||||
} else if (!stack.length && s[i] === ",") {
|
||||
const token = s.substring(start, i).trim();
|
||||
if (token)
|
||||
result.push(token);
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
const lastToken = s.substring(start).trim();
|
||||
if (lastToken)
|
||||
result.push(lastToken);
|
||||
return result;
|
||||
}
|
||||
const registrationIdMap = /* @__PURE__ */ new Map();
|
||||
let lastId = 0;
|
||||
function registrationId(registration) {
|
||||
if (registration.id)
|
||||
return registration.id;
|
||||
const key = registration.name + "@@@" + (registration.super ? registrationId(registration.super) : "");
|
||||
let map = registrationIdMap.get(key);
|
||||
if (!map) {
|
||||
map = /* @__PURE__ */ new Map();
|
||||
registrationIdMap.set(key, map);
|
||||
}
|
||||
if (!map.has(registration.fn))
|
||||
map.set(registration.fn, String(lastId++));
|
||||
registration.id = map.get(registration.fn);
|
||||
return registration.id;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
FixturePool,
|
||||
fixtureParameterNames,
|
||||
formatPotentiallyInternalLocation,
|
||||
inheritFixtureNames
|
||||
});
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var globals_exports = {};
|
||||
__export(globals_exports, {
|
||||
currentTestInfo: () => currentTestInfo,
|
||||
currentlyLoadingFileSuite: () => currentlyLoadingFileSuite,
|
||||
isWorkerProcess: () => isWorkerProcess,
|
||||
setCurrentTestInfo: () => setCurrentTestInfo,
|
||||
setCurrentlyLoadingFileSuite: () => setCurrentlyLoadingFileSuite,
|
||||
setIsWorkerProcess: () => setIsWorkerProcess
|
||||
});
|
||||
module.exports = __toCommonJS(globals_exports);
|
||||
let currentTestInfoValue = null;
|
||||
function setCurrentTestInfo(testInfo) {
|
||||
currentTestInfoValue = testInfo;
|
||||
}
|
||||
function currentTestInfo() {
|
||||
return currentTestInfoValue;
|
||||
}
|
||||
let currentFileSuite;
|
||||
function setCurrentlyLoadingFileSuite(suite) {
|
||||
currentFileSuite = suite;
|
||||
}
|
||||
function currentlyLoadingFileSuite() {
|
||||
return currentFileSuite;
|
||||
}
|
||||
let _isWorkerProcess = false;
|
||||
function setIsWorkerProcess() {
|
||||
_isWorkerProcess = true;
|
||||
}
|
||||
function isWorkerProcess() {
|
||||
return _isWorkerProcess;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
currentTestInfo,
|
||||
currentlyLoadingFileSuite,
|
||||
isWorkerProcess,
|
||||
setCurrentTestInfo,
|
||||
setCurrentlyLoadingFileSuite,
|
||||
setIsWorkerProcess
|
||||
});
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var ipc_exports = {};
|
||||
__export(ipc_exports, {
|
||||
serializeConfig: () => serializeConfig,
|
||||
stdioChunkToParams: () => stdioChunkToParams
|
||||
});
|
||||
module.exports = __toCommonJS(ipc_exports);
|
||||
var import_util = __toESM(require("util"));
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
function serializeConfig(config, passCompilationCache) {
|
||||
const result = {
|
||||
location: { configDir: config.configDir, resolvedConfigFile: config.config.configFile },
|
||||
configCLIOverrides: config.configCLIOverrides,
|
||||
compilationCache: passCompilationCache ? (0, import_compilationCache.serializeCompilationCache)() : void 0
|
||||
};
|
||||
try {
|
||||
result.metadata = JSON.stringify(config.config.metadata);
|
||||
} catch (error) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function stdioChunkToParams(chunk) {
|
||||
if (chunk instanceof Uint8Array)
|
||||
return { buffer: Buffer.from(chunk).toString("base64") };
|
||||
if (typeof chunk !== "string")
|
||||
return { text: import_util.default.inspect(chunk) };
|
||||
return { text: chunk };
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
serializeConfig,
|
||||
stdioChunkToParams
|
||||
});
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var poolBuilder_exports = {};
|
||||
__export(poolBuilder_exports, {
|
||||
PoolBuilder: () => PoolBuilder
|
||||
});
|
||||
module.exports = __toCommonJS(poolBuilder_exports);
|
||||
var import_fixtures = require("./fixtures");
|
||||
var import_util = require("../util");
|
||||
class PoolBuilder {
|
||||
constructor(type, project) {
|
||||
this._testTypePools = /* @__PURE__ */ new Map();
|
||||
this._type = type;
|
||||
this._project = project;
|
||||
}
|
||||
static createForLoader() {
|
||||
return new PoolBuilder("loader");
|
||||
}
|
||||
static createForWorker(project) {
|
||||
return new PoolBuilder("worker", project);
|
||||
}
|
||||
buildPools(suite, testErrors) {
|
||||
suite.forEachTest((test) => {
|
||||
const pool = this._buildPoolForTest(test, testErrors);
|
||||
if (this._type === "loader")
|
||||
test._poolDigest = pool.digest;
|
||||
if (this._type === "worker")
|
||||
test._pool = pool;
|
||||
});
|
||||
}
|
||||
_buildPoolForTest(test, testErrors) {
|
||||
let pool = this._buildTestTypePool(test._testType, testErrors);
|
||||
const parents = [];
|
||||
for (let parent = test.parent; parent; parent = parent.parent)
|
||||
parents.push(parent);
|
||||
parents.reverse();
|
||||
for (const parent of parents) {
|
||||
if (parent._use.length)
|
||||
pool = new import_fixtures.FixturePool(parent._use, (e) => this._handleLoadError(e, testErrors), pool, parent._type === "describe");
|
||||
for (const hook of parent._hooks)
|
||||
pool.validateFunction(hook.fn, hook.type + " hook", hook.location);
|
||||
for (const modifier of parent._modifiers)
|
||||
pool.validateFunction(modifier.fn, modifier.type + " modifier", modifier.location);
|
||||
}
|
||||
pool.validateFunction(test.fn, "Test", test.location);
|
||||
return pool;
|
||||
}
|
||||
_buildTestTypePool(testType, testErrors) {
|
||||
if (!this._testTypePools.has(testType)) {
|
||||
const optionOverrides = {
|
||||
overrides: this._project?.project?.use ?? {},
|
||||
location: { file: `project#${this._project?.id}`, line: 1, column: 1 }
|
||||
};
|
||||
const pool = new import_fixtures.FixturePool(testType.fixtures, (e) => this._handleLoadError(e, testErrors), void 0, void 0, optionOverrides);
|
||||
this._testTypePools.set(testType, pool);
|
||||
}
|
||||
return this._testTypePools.get(testType);
|
||||
}
|
||||
_handleLoadError(e, testErrors) {
|
||||
if (testErrors)
|
||||
testErrors.push(e);
|
||||
else
|
||||
throw new Error(`${(0, import_util.formatLocation)(e.location)}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
PoolBuilder
|
||||
});
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var process_exports = {};
|
||||
__export(process_exports, {
|
||||
ProcessRunner: () => ProcessRunner
|
||||
});
|
||||
module.exports = __toCommonJS(process_exports);
|
||||
var import_bootstrap = require("playwright-core/lib/bootstrap");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
class ProcessRunner {
|
||||
async gracefullyClose() {
|
||||
}
|
||||
dispatchEvent(method, params) {
|
||||
const response = { method, params };
|
||||
sendMessageToParent({ method: "__dispatch__", params: response });
|
||||
}
|
||||
async sendRequest(method, params) {
|
||||
return await sendRequestToParent(method, params);
|
||||
}
|
||||
async sendMessageNoReply(method, params) {
|
||||
void sendRequestToParent(method, params).catch(() => {
|
||||
});
|
||||
}
|
||||
}
|
||||
let gracefullyCloseCalled = false;
|
||||
let forceExitInitiated = false;
|
||||
sendMessageToParent({ method: "ready" });
|
||||
process.on("disconnect", () => gracefullyCloseAndExit(true));
|
||||
process.on("SIGINT", () => {
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
});
|
||||
let processRunner;
|
||||
let processName;
|
||||
const startingEnv = { ...process.env };
|
||||
process.on("message", async (message) => {
|
||||
if (message.method === "__init__") {
|
||||
const { processParams, runnerParams, runnerScript } = message.params;
|
||||
void (0, import_utils.startProfiling)();
|
||||
(0, import_utils.setTimeOrigin)(processParams.timeOrigin);
|
||||
const { create } = require(runnerScript);
|
||||
processRunner = create(runnerParams);
|
||||
processName = processParams.processName;
|
||||
return;
|
||||
}
|
||||
if (message.method === "__stop__") {
|
||||
const keys = /* @__PURE__ */ new Set([...Object.keys(process.env), ...Object.keys(startingEnv)]);
|
||||
const producedEnv = [...keys].filter((key) => startingEnv[key] !== process.env[key]).map((key) => [key, process.env[key] ?? null]);
|
||||
sendMessageToParent({ method: "__env_produced__", params: producedEnv });
|
||||
await gracefullyCloseAndExit(false);
|
||||
return;
|
||||
}
|
||||
if (message.method === "__dispatch__") {
|
||||
const { id, method, params } = message.params;
|
||||
try {
|
||||
const result = await processRunner[method](params);
|
||||
const response = { id, result };
|
||||
sendMessageToParent({ method: "__dispatch__", params: response });
|
||||
} catch (e) {
|
||||
const response = { id, error: (0, import_util.serializeError)(e) };
|
||||
sendMessageToParent({ method: "__dispatch__", params: response });
|
||||
}
|
||||
}
|
||||
if (message.method === "__response__")
|
||||
handleResponseFromParent(message.params);
|
||||
});
|
||||
const kForceExitTimeout = +(process.env.PWTEST_FORCE_EXIT_TIMEOUT || 3e4);
|
||||
async function gracefullyCloseAndExit(forceExit) {
|
||||
if (forceExit && !forceExitInitiated) {
|
||||
forceExitInitiated = true;
|
||||
setTimeout(() => process.exit(0), kForceExitTimeout);
|
||||
}
|
||||
if (!gracefullyCloseCalled) {
|
||||
gracefullyCloseCalled = true;
|
||||
await processRunner?.gracefullyClose().catch(() => {
|
||||
});
|
||||
if (processName)
|
||||
await (0, import_utils.stopProfiling)(processName).catch(() => {
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
function sendMessageToParent(message) {
|
||||
try {
|
||||
process.send(message);
|
||||
} catch (e) {
|
||||
try {
|
||||
JSON.stringify(message);
|
||||
} catch {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
let lastId = 0;
|
||||
const requestCallbacks = /* @__PURE__ */ new Map();
|
||||
async function sendRequestToParent(method, params) {
|
||||
const id = ++lastId;
|
||||
sendMessageToParent({ method: "__request__", params: { id, method, params } });
|
||||
const promise = new import_utils.ManualPromise();
|
||||
requestCallbacks.set(id, promise);
|
||||
return promise;
|
||||
}
|
||||
function handleResponseFromParent(response) {
|
||||
const promise = requestCallbacks.get(response.id);
|
||||
if (!promise)
|
||||
return;
|
||||
requestCallbacks.delete(response.id);
|
||||
if (response.error)
|
||||
promise.reject(new Error(response.error.message));
|
||||
else
|
||||
promise.resolve(response.result);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
ProcessRunner
|
||||
});
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var suiteUtils_exports = {};
|
||||
__export(suiteUtils_exports, {
|
||||
applyRepeatEachIndex: () => applyRepeatEachIndex,
|
||||
bindFileSuiteToProject: () => bindFileSuiteToProject,
|
||||
filterByFocusedLine: () => filterByFocusedLine,
|
||||
filterOnly: () => filterOnly,
|
||||
filterSuite: () => filterSuite,
|
||||
filterTestsRemoveEmptySuites: () => filterTestsRemoveEmptySuites
|
||||
});
|
||||
module.exports = __toCommonJS(suiteUtils_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
function filterSuite(suite, suiteFilter, testFilter) {
|
||||
for (const child of suite.suites) {
|
||||
if (!suiteFilter(child))
|
||||
filterSuite(child, suiteFilter, testFilter);
|
||||
}
|
||||
const filteredTests = suite.tests.filter(testFilter);
|
||||
const entries = /* @__PURE__ */ new Set([...suite.suites, ...filteredTests]);
|
||||
suite._entries = suite._entries.filter((e) => entries.has(e));
|
||||
}
|
||||
function filterTestsRemoveEmptySuites(suite, filter) {
|
||||
const filteredSuites = suite.suites.filter((child) => filterTestsRemoveEmptySuites(child, filter));
|
||||
const filteredTests = suite.tests.filter(filter);
|
||||
const entries = /* @__PURE__ */ new Set([...filteredSuites, ...filteredTests]);
|
||||
suite._entries = suite._entries.filter((e) => entries.has(e));
|
||||
return !!suite._entries.length;
|
||||
}
|
||||
function bindFileSuiteToProject(project, suite) {
|
||||
const relativeFile = import_path.default.relative(project.project.testDir, suite.location.file);
|
||||
const fileId = (0, import_utils.calculateSha1)((0, import_utils.toPosixPath)(relativeFile)).slice(0, 20);
|
||||
const result = suite._deepClone();
|
||||
result._fileId = fileId;
|
||||
result.forEachTest((test, suite2) => {
|
||||
suite2._fileId = fileId;
|
||||
const [file, ...titles] = test.titlePath();
|
||||
const testIdExpression = `[project=${project.id}]${(0, import_utils.toPosixPath)(file)}${titles.join("")}`;
|
||||
const testId = fileId + "-" + (0, import_utils.calculateSha1)(testIdExpression).slice(0, 20);
|
||||
test.id = testId;
|
||||
test._projectId = project.id;
|
||||
let inheritedRetries;
|
||||
let inheritedTimeout;
|
||||
for (let parentSuite = suite2; parentSuite; parentSuite = parentSuite.parent) {
|
||||
if (parentSuite._staticAnnotations.length)
|
||||
test.annotations.unshift(...parentSuite._staticAnnotations);
|
||||
if (inheritedRetries === void 0 && parentSuite._retries !== void 0)
|
||||
inheritedRetries = parentSuite._retries;
|
||||
if (inheritedTimeout === void 0 && parentSuite._timeout !== void 0)
|
||||
inheritedTimeout = parentSuite._timeout;
|
||||
}
|
||||
test.retries = inheritedRetries ?? project.project.retries;
|
||||
test.timeout = inheritedTimeout ?? project.project.timeout;
|
||||
if (test.annotations.some((a) => a.type === "skip" || a.type === "fixme"))
|
||||
test.expectedStatus = "skipped";
|
||||
if (test._poolDigest)
|
||||
test._workerHash = `${project.id}-${test._poolDigest}-0`;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function applyRepeatEachIndex(project, fileSuite, repeatEachIndex) {
|
||||
fileSuite.forEachTest((test, suite) => {
|
||||
if (repeatEachIndex) {
|
||||
const [file, ...titles] = test.titlePath();
|
||||
const testIdExpression = `[project=${project.id}]${(0, import_utils.toPosixPath)(file)}${titles.join("")} (repeat:${repeatEachIndex})`;
|
||||
const testId = suite._fileId + "-" + (0, import_utils.calculateSha1)(testIdExpression).slice(0, 20);
|
||||
test.id = testId;
|
||||
test.repeatEachIndex = repeatEachIndex;
|
||||
if (test._poolDigest)
|
||||
test._workerHash = `${project.id}-${test._poolDigest}-${repeatEachIndex}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
function filterOnly(suite) {
|
||||
if (!suite._getOnlyItems().length)
|
||||
return;
|
||||
const suiteFilter = (suite2) => suite2._only;
|
||||
const testFilter = (test) => test._only;
|
||||
return filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter);
|
||||
}
|
||||
function filterSuiteWithOnlySemantics(suite, suiteFilter, testFilter) {
|
||||
const onlySuites = suite.suites.filter((child) => filterSuiteWithOnlySemantics(child, suiteFilter, testFilter) || suiteFilter(child));
|
||||
const onlyTests = suite.tests.filter(testFilter);
|
||||
const onlyEntries = /* @__PURE__ */ new Set([...onlySuites, ...onlyTests]);
|
||||
if (onlyEntries.size) {
|
||||
suite._entries = suite._entries.filter((e) => onlyEntries.has(e));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function filterByFocusedLine(suite, focusedTestFileLines) {
|
||||
if (!focusedTestFileLines.length)
|
||||
return;
|
||||
const matchers = focusedTestFileLines.map(createFileMatcherFromFilter);
|
||||
const testFileLineMatches = (testFileName, testLine, testColumn) => matchers.some((m) => m(testFileName, testLine, testColumn));
|
||||
const suiteFilter = (suite2) => !!suite2.location && testFileLineMatches(suite2.location.file, suite2.location.line, suite2.location.column);
|
||||
const testFilter = (test) => testFileLineMatches(test.location.file, test.location.line, test.location.column);
|
||||
return filterSuite(suite, suiteFilter, testFilter);
|
||||
}
|
||||
function createFileMatcherFromFilter(filter) {
|
||||
const fileMatcher = (0, import_util.createFileMatcher)(filter.re || filter.exact || "");
|
||||
return (testFileName, testLine, testColumn) => fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
applyRepeatEachIndex,
|
||||
bindFileSuiteToProject,
|
||||
filterByFocusedLine,
|
||||
filterOnly,
|
||||
filterSuite,
|
||||
filterTestsRemoveEmptySuites
|
||||
});
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var test_exports = {};
|
||||
__export(test_exports, {
|
||||
Suite: () => Suite,
|
||||
TestCase: () => TestCase
|
||||
});
|
||||
module.exports = __toCommonJS(test_exports);
|
||||
var import_testType = require("./testType");
|
||||
var import_teleReceiver = require("../isomorphic/teleReceiver");
|
||||
class Base {
|
||||
constructor(title) {
|
||||
this._only = false;
|
||||
this._requireFile = "";
|
||||
this.title = title;
|
||||
}
|
||||
}
|
||||
class Suite extends Base {
|
||||
constructor(title, type) {
|
||||
super(title);
|
||||
this._use = [];
|
||||
this._entries = [];
|
||||
this._hooks = [];
|
||||
// Annotations known statically before running the test, e.g. `test.describe.skip()` or `test.describe({ annotation }, body)`.
|
||||
this._staticAnnotations = [];
|
||||
// Explicitly declared tags that are not a part of the title.
|
||||
this._tags = [];
|
||||
this._modifiers = [];
|
||||
this._parallelMode = "none";
|
||||
this._type = type;
|
||||
}
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
entries() {
|
||||
return this._entries;
|
||||
}
|
||||
get suites() {
|
||||
return this._entries.filter((entry) => entry instanceof Suite);
|
||||
}
|
||||
get tests() {
|
||||
return this._entries.filter((entry) => entry instanceof TestCase);
|
||||
}
|
||||
_addTest(test) {
|
||||
test.parent = this;
|
||||
this._entries.push(test);
|
||||
}
|
||||
_addSuite(suite) {
|
||||
suite.parent = this;
|
||||
this._entries.push(suite);
|
||||
}
|
||||
_prependSuite(suite) {
|
||||
suite.parent = this;
|
||||
this._entries.unshift(suite);
|
||||
}
|
||||
allTests() {
|
||||
const result = [];
|
||||
const visit = (suite) => {
|
||||
for (const entry of suite._entries) {
|
||||
if (entry instanceof Suite)
|
||||
visit(entry);
|
||||
else
|
||||
result.push(entry);
|
||||
}
|
||||
};
|
||||
visit(this);
|
||||
return result;
|
||||
}
|
||||
_hasTests() {
|
||||
let result = false;
|
||||
const visit = (suite) => {
|
||||
for (const entry of suite._entries) {
|
||||
if (result)
|
||||
return;
|
||||
if (entry instanceof Suite)
|
||||
visit(entry);
|
||||
else
|
||||
result = true;
|
||||
}
|
||||
};
|
||||
visit(this);
|
||||
return result;
|
||||
}
|
||||
titlePath() {
|
||||
const titlePath = this.parent ? this.parent.titlePath() : [];
|
||||
if (this.title || this._type !== "describe")
|
||||
titlePath.push(this.title);
|
||||
return titlePath;
|
||||
}
|
||||
_collectGrepTitlePath(path) {
|
||||
if (this.parent)
|
||||
this.parent._collectGrepTitlePath(path);
|
||||
if (this.title || this._type !== "describe")
|
||||
path.push(this.title);
|
||||
path.push(...this._tags);
|
||||
}
|
||||
_collectTagTitlePath(path) {
|
||||
this.parent?._collectTagTitlePath(path);
|
||||
if (this._type === "describe")
|
||||
path.push(this.title);
|
||||
path.push(...this._tags);
|
||||
}
|
||||
_getOnlyItems() {
|
||||
const items = [];
|
||||
if (this._only)
|
||||
items.push(this);
|
||||
for (const suite of this.suites)
|
||||
items.push(...suite._getOnlyItems());
|
||||
items.push(...this.tests.filter((test) => test._only));
|
||||
return items;
|
||||
}
|
||||
_deepClone() {
|
||||
const suite = this._clone();
|
||||
for (const entry of this._entries) {
|
||||
if (entry instanceof Suite)
|
||||
suite._addSuite(entry._deepClone());
|
||||
else
|
||||
suite._addTest(entry._clone());
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
_deepSerialize() {
|
||||
const suite = this._serialize();
|
||||
suite.entries = [];
|
||||
for (const entry of this._entries) {
|
||||
if (entry instanceof Suite)
|
||||
suite.entries.push(entry._deepSerialize());
|
||||
else
|
||||
suite.entries.push(entry._serialize());
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
static _deepParse(data) {
|
||||
const suite = Suite._parse(data);
|
||||
for (const entry of data.entries) {
|
||||
if (entry.kind === "suite")
|
||||
suite._addSuite(Suite._deepParse(entry));
|
||||
else
|
||||
suite._addTest(TestCase._parse(entry));
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
forEachTest(visitor) {
|
||||
for (const entry of this._entries) {
|
||||
if (entry instanceof Suite)
|
||||
entry.forEachTest(visitor);
|
||||
else
|
||||
visitor(entry, this);
|
||||
}
|
||||
}
|
||||
_serialize() {
|
||||
return {
|
||||
kind: "suite",
|
||||
title: this.title,
|
||||
type: this._type,
|
||||
location: this.location,
|
||||
only: this._only,
|
||||
requireFile: this._requireFile,
|
||||
timeout: this._timeout,
|
||||
retries: this._retries,
|
||||
staticAnnotations: this._staticAnnotations.slice(),
|
||||
tags: this._tags.slice(),
|
||||
modifiers: this._modifiers.slice(),
|
||||
parallelMode: this._parallelMode,
|
||||
hooks: this._hooks.map((h) => ({ type: h.type, location: h.location, title: h.title })),
|
||||
fileId: this._fileId
|
||||
};
|
||||
}
|
||||
static _parse(data) {
|
||||
const suite = new Suite(data.title, data.type);
|
||||
suite.location = data.location;
|
||||
suite._only = data.only;
|
||||
suite._requireFile = data.requireFile;
|
||||
suite._timeout = data.timeout;
|
||||
suite._retries = data.retries;
|
||||
suite._staticAnnotations = data.staticAnnotations;
|
||||
suite._tags = data.tags;
|
||||
suite._modifiers = data.modifiers;
|
||||
suite._parallelMode = data.parallelMode;
|
||||
suite._hooks = data.hooks.map((h) => ({ type: h.type, location: h.location, title: h.title, fn: () => {
|
||||
} }));
|
||||
suite._fileId = data.fileId;
|
||||
return suite;
|
||||
}
|
||||
_clone() {
|
||||
const data = this._serialize();
|
||||
const suite = Suite._parse(data);
|
||||
suite._use = this._use.slice();
|
||||
suite._hooks = this._hooks.slice();
|
||||
suite._fullProject = this._fullProject;
|
||||
return suite;
|
||||
}
|
||||
project() {
|
||||
return this._fullProject?.project || this.parent?.project();
|
||||
}
|
||||
}
|
||||
class TestCase extends Base {
|
||||
constructor(title, fn, testType, location) {
|
||||
super(title);
|
||||
this.results = [];
|
||||
this.type = "test";
|
||||
this.expectedStatus = "passed";
|
||||
this.timeout = 0;
|
||||
this.annotations = [];
|
||||
this.retries = 0;
|
||||
this.repeatEachIndex = 0;
|
||||
this.id = "";
|
||||
this._poolDigest = "";
|
||||
this._workerHash = "";
|
||||
this._projectId = "";
|
||||
// Explicitly declared tags that are not a part of the title.
|
||||
this._tags = [];
|
||||
this.fn = fn;
|
||||
this._testType = testType;
|
||||
this.location = location;
|
||||
}
|
||||
titlePath() {
|
||||
const titlePath = this.parent ? this.parent.titlePath() : [];
|
||||
titlePath.push(this.title);
|
||||
return titlePath;
|
||||
}
|
||||
outcome() {
|
||||
return (0, import_teleReceiver.computeTestCaseOutcome)(this);
|
||||
}
|
||||
ok() {
|
||||
const status = this.outcome();
|
||||
return status === "expected" || status === "flaky" || status === "skipped";
|
||||
}
|
||||
get tags() {
|
||||
const path = [];
|
||||
this.parent._collectTagTitlePath(path);
|
||||
path.push(this.title);
|
||||
const titleTags = path.join(" ").match(/@[\S]+/g) || [];
|
||||
return [
|
||||
...titleTags,
|
||||
...this._tags
|
||||
];
|
||||
}
|
||||
_serialize() {
|
||||
return {
|
||||
kind: "test",
|
||||
id: this.id,
|
||||
title: this.title,
|
||||
retries: this.retries,
|
||||
timeout: this.timeout,
|
||||
expectedStatus: this.expectedStatus,
|
||||
location: this.location,
|
||||
only: this._only,
|
||||
requireFile: this._requireFile,
|
||||
poolDigest: this._poolDigest,
|
||||
workerHash: this._workerHash,
|
||||
annotations: this.annotations.slice(),
|
||||
tags: this._tags.slice(),
|
||||
projectId: this._projectId
|
||||
};
|
||||
}
|
||||
static _parse(data) {
|
||||
const test = new TestCase(data.title, () => {
|
||||
}, import_testType.rootTestType, data.location);
|
||||
test.id = data.id;
|
||||
test.retries = data.retries;
|
||||
test.timeout = data.timeout;
|
||||
test.expectedStatus = data.expectedStatus;
|
||||
test._only = data.only;
|
||||
test._requireFile = data.requireFile;
|
||||
test._poolDigest = data.poolDigest;
|
||||
test._workerHash = data.workerHash;
|
||||
test.annotations = data.annotations;
|
||||
test._tags = data.tags;
|
||||
test._projectId = data.projectId;
|
||||
return test;
|
||||
}
|
||||
_clone() {
|
||||
const data = this._serialize();
|
||||
const test = TestCase._parse(data);
|
||||
test._testType = this._testType;
|
||||
test.fn = this.fn;
|
||||
return test;
|
||||
}
|
||||
_appendTestResult() {
|
||||
const result = {
|
||||
retry: this.results.length,
|
||||
parallelIndex: -1,
|
||||
workerIndex: -1,
|
||||
duration: 0,
|
||||
startTime: /* @__PURE__ */ new Date(),
|
||||
stdout: [],
|
||||
stderr: [],
|
||||
attachments: [],
|
||||
status: "skipped",
|
||||
steps: [],
|
||||
errors: [],
|
||||
annotations: []
|
||||
};
|
||||
this.results.push(result);
|
||||
return result;
|
||||
}
|
||||
_grepBaseTitlePath() {
|
||||
const path = [];
|
||||
this.parent._collectGrepTitlePath(path);
|
||||
path.push(this.title);
|
||||
return path;
|
||||
}
|
||||
_grepTitleWithTags() {
|
||||
const path = this._grepBaseTitlePath();
|
||||
path.push(...this._tags);
|
||||
return path.join(" ");
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Suite,
|
||||
TestCase
|
||||
});
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testLoader_exports = {};
|
||||
__export(testLoader_exports, {
|
||||
defaultTimeout: () => defaultTimeout,
|
||||
loadTestFile: () => loadTestFile
|
||||
});
|
||||
module.exports = __toCommonJS(testLoader_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_util = __toESM(require("util"));
|
||||
var esmLoaderHost = __toESM(require("./esmLoaderHost"));
|
||||
var import_globals = require("./globals");
|
||||
var import_test = require("./test");
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
var import_transform = require("../transform/transform");
|
||||
var import_util2 = require("../util");
|
||||
const defaultTimeout = 3e4;
|
||||
const cachedFileSuites = /* @__PURE__ */ new Map();
|
||||
async function loadTestFile(file, config, testErrors) {
|
||||
if (cachedFileSuites.has(file))
|
||||
return cachedFileSuites.get(file);
|
||||
const suite = new import_test.Suite(import_path.default.relative(config.config.rootDir, file) || import_path.default.basename(file), "file");
|
||||
suite._requireFile = file;
|
||||
suite.location = { file, line: 0, column: 0 };
|
||||
suite._tags = [...config.config.tags];
|
||||
(0, import_globals.setCurrentlyLoadingFileSuite)(suite);
|
||||
if (!(0, import_globals.isWorkerProcess)()) {
|
||||
(0, import_compilationCache.startCollectingFileDeps)();
|
||||
await esmLoaderHost.startCollectingFileDeps();
|
||||
}
|
||||
try {
|
||||
await (0, import_transform.requireOrImport)(file);
|
||||
cachedFileSuites.set(file, suite);
|
||||
} catch (e) {
|
||||
if (!testErrors)
|
||||
throw e;
|
||||
testErrors.push(serializeLoadError(file, e));
|
||||
} finally {
|
||||
(0, import_globals.setCurrentlyLoadingFileSuite)(void 0);
|
||||
if (!(0, import_globals.isWorkerProcess)()) {
|
||||
(0, import_compilationCache.stopCollectingFileDeps)(file);
|
||||
await esmLoaderHost.stopCollectingFileDeps(file);
|
||||
}
|
||||
}
|
||||
{
|
||||
const files = /* @__PURE__ */ new Set();
|
||||
suite.allTests().map((t) => files.add(t.location.file));
|
||||
if (files.size === 1) {
|
||||
const mappedFile = files.values().next().value;
|
||||
if (suite.location.file !== mappedFile) {
|
||||
if (import_path.default.extname(mappedFile) !== import_path.default.extname(suite.location.file))
|
||||
suite.location.file = mappedFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
function serializeLoadError(file, error) {
|
||||
if (error instanceof Error) {
|
||||
const result = (0, import_util2.filterStackTrace)(error);
|
||||
const loc = error.loc;
|
||||
result.location = loc ? {
|
||||
file,
|
||||
line: loc.line || 0,
|
||||
column: loc.column || 0
|
||||
} : void 0;
|
||||
return result;
|
||||
}
|
||||
return { value: import_util.default.inspect(error) };
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
defaultTimeout,
|
||||
loadTestFile
|
||||
});
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testType_exports = {};
|
||||
__export(testType_exports, {
|
||||
TestTypeImpl: () => TestTypeImpl,
|
||||
mergeTests: () => mergeTests,
|
||||
rootTestType: () => rootTestType
|
||||
});
|
||||
module.exports = __toCommonJS(testType_exports);
|
||||
var import_playwright_core = require("playwright-core");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_globals = require("./globals");
|
||||
var import_test = require("./test");
|
||||
var import_expect = require("../matchers/expect");
|
||||
var import_transform = require("../transform/transform");
|
||||
var import_validators = require("./validators");
|
||||
const testTypeSymbol = Symbol("testType");
|
||||
class TestTypeImpl {
|
||||
constructor(fixtures) {
|
||||
this.fixtures = fixtures;
|
||||
const test = (0, import_transform.wrapFunctionWithLocation)(this._createTest.bind(this, "default"));
|
||||
test[testTypeSymbol] = this;
|
||||
test.expect = import_expect.expect;
|
||||
test.only = (0, import_transform.wrapFunctionWithLocation)(this._createTest.bind(this, "only"));
|
||||
test.describe = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "default"));
|
||||
test.describe.only = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "only"));
|
||||
test.describe.configure = (0, import_transform.wrapFunctionWithLocation)(this._configure.bind(this));
|
||||
test.describe.fixme = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "fixme"));
|
||||
test.describe.parallel = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "parallel"));
|
||||
test.describe.parallel.only = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "parallel.only"));
|
||||
test.describe.serial = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "serial"));
|
||||
test.describe.serial.only = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "serial.only"));
|
||||
test.describe.skip = (0, import_transform.wrapFunctionWithLocation)(this._describe.bind(this, "skip"));
|
||||
test.beforeEach = (0, import_transform.wrapFunctionWithLocation)(this._hook.bind(this, "beforeEach"));
|
||||
test.afterEach = (0, import_transform.wrapFunctionWithLocation)(this._hook.bind(this, "afterEach"));
|
||||
test.beforeAll = (0, import_transform.wrapFunctionWithLocation)(this._hook.bind(this, "beforeAll"));
|
||||
test.afterAll = (0, import_transform.wrapFunctionWithLocation)(this._hook.bind(this, "afterAll"));
|
||||
test.skip = (0, import_transform.wrapFunctionWithLocation)(this._modifier.bind(this, "skip"));
|
||||
test.fixme = (0, import_transform.wrapFunctionWithLocation)(this._modifier.bind(this, "fixme"));
|
||||
test.fail = (0, import_transform.wrapFunctionWithLocation)(this._modifier.bind(this, "fail"));
|
||||
test.fail.only = (0, import_transform.wrapFunctionWithLocation)(this._createTest.bind(this, "fail.only"));
|
||||
test.slow = (0, import_transform.wrapFunctionWithLocation)(this._modifier.bind(this, "slow"));
|
||||
test.setTimeout = (0, import_transform.wrapFunctionWithLocation)(this._setTimeout.bind(this));
|
||||
test.step = this._step.bind(this, "pass");
|
||||
test.step.skip = this._step.bind(this, "skip");
|
||||
test.use = (0, import_transform.wrapFunctionWithLocation)(this._use.bind(this));
|
||||
test.extend = (0, import_transform.wrapFunctionWithLocation)(this._extend.bind(this));
|
||||
test.info = () => {
|
||||
const result = (0, import_globals.currentTestInfo)();
|
||||
if (!result)
|
||||
throw new Error("test.info() can only be called while test is running");
|
||||
return result;
|
||||
};
|
||||
this.test = test;
|
||||
}
|
||||
_currentSuite(location, title) {
|
||||
const suite = (0, import_globals.currentlyLoadingFileSuite)();
|
||||
if (!suite) {
|
||||
throw new Error([
|
||||
`Playwright Test did not expect ${title} to be called here.`,
|
||||
`Most common reasons include:`,
|
||||
`- You are calling ${title} in a configuration file.`,
|
||||
`- You are calling ${title} in a file that is imported by the configuration file.`,
|
||||
`- You have two different versions of @playwright/test. This usually happens`,
|
||||
` when one of the dependencies in your package.json depends on @playwright/test.`
|
||||
].join("\n"));
|
||||
}
|
||||
return suite;
|
||||
}
|
||||
_createTest(type, location, title, fnOrDetails, fn) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._currentSuite(location, "test()");
|
||||
if (!suite)
|
||||
return;
|
||||
let details;
|
||||
let body;
|
||||
if (typeof fnOrDetails === "function") {
|
||||
body = fnOrDetails;
|
||||
details = {};
|
||||
} else {
|
||||
body = fn;
|
||||
details = fnOrDetails;
|
||||
}
|
||||
const validatedDetails = (0, import_validators.validateTestDetails)(details, location);
|
||||
const test = new import_test.TestCase(title, body, this, location);
|
||||
test._requireFile = suite._requireFile;
|
||||
test.annotations.push(...validatedDetails.annotations);
|
||||
test._tags.push(...validatedDetails.tags);
|
||||
suite._addTest(test);
|
||||
if (type === "only" || type === "fail.only")
|
||||
test._only = true;
|
||||
if (type === "skip" || type === "fixme" || type === "fail")
|
||||
test.annotations.push({ type, location });
|
||||
else if (type === "fail.only")
|
||||
test.annotations.push({ type: "fail", location });
|
||||
}
|
||||
_describe(type, location, titleOrFn, fnOrDetails, fn) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._currentSuite(location, "test.describe()");
|
||||
if (!suite)
|
||||
return;
|
||||
let title;
|
||||
let body;
|
||||
let details;
|
||||
if (typeof titleOrFn === "function") {
|
||||
title = "";
|
||||
details = {};
|
||||
body = titleOrFn;
|
||||
} else if (typeof fnOrDetails === "function") {
|
||||
title = titleOrFn;
|
||||
details = {};
|
||||
body = fnOrDetails;
|
||||
} else {
|
||||
title = titleOrFn;
|
||||
details = fnOrDetails;
|
||||
body = fn;
|
||||
}
|
||||
const validatedDetails = (0, import_validators.validateTestDetails)(details, location);
|
||||
const child = new import_test.Suite(title, "describe");
|
||||
child._requireFile = suite._requireFile;
|
||||
child.location = location;
|
||||
child._staticAnnotations.push(...validatedDetails.annotations);
|
||||
child._tags.push(...validatedDetails.tags);
|
||||
suite._addSuite(child);
|
||||
if (type === "only" || type === "serial.only" || type === "parallel.only")
|
||||
child._only = true;
|
||||
if (type === "serial" || type === "serial.only")
|
||||
child._parallelMode = "serial";
|
||||
if (type === "parallel" || type === "parallel.only")
|
||||
child._parallelMode = "parallel";
|
||||
if (type === "skip" || type === "fixme")
|
||||
child._staticAnnotations.push({ type, location });
|
||||
for (let parent = suite; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === "serial" && child._parallelMode === "parallel")
|
||||
throw new Error("describe.parallel cannot be nested inside describe.serial");
|
||||
if (parent._parallelMode === "default" && child._parallelMode === "parallel")
|
||||
throw new Error("describe.parallel cannot be nested inside describe with default mode");
|
||||
}
|
||||
(0, import_globals.setCurrentlyLoadingFileSuite)(child);
|
||||
body();
|
||||
(0, import_globals.setCurrentlyLoadingFileSuite)(suite);
|
||||
}
|
||||
_hook(name, location, title, fn) {
|
||||
const suite = this._currentSuite(location, `test.${name}()`);
|
||||
if (!suite)
|
||||
return;
|
||||
if (typeof title === "function") {
|
||||
fn = title;
|
||||
title = `${name} hook`;
|
||||
}
|
||||
suite._hooks.push({ type: name, fn, title, location });
|
||||
}
|
||||
_configure(location, options) {
|
||||
throwIfRunningInsideJest();
|
||||
const suite = this._currentSuite(location, `test.describe.configure()`);
|
||||
if (!suite)
|
||||
return;
|
||||
if (options.timeout !== void 0)
|
||||
suite._timeout = options.timeout;
|
||||
if (options.retries !== void 0)
|
||||
suite._retries = options.retries;
|
||||
if (options.mode !== void 0) {
|
||||
if (suite._parallelMode !== "none")
|
||||
throw new Error(`"${suite._parallelMode}" mode is already assigned for the enclosing scope.`);
|
||||
suite._parallelMode = options.mode;
|
||||
for (let parent = suite.parent; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === "serial" && suite._parallelMode === "parallel")
|
||||
throw new Error("describe with parallel mode cannot be nested inside describe with serial mode");
|
||||
if (parent._parallelMode === "default" && suite._parallelMode === "parallel")
|
||||
throw new Error("describe with parallel mode cannot be nested inside describe with default mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
_modifier(type, location, ...modifierArgs) {
|
||||
const suite = (0, import_globals.currentlyLoadingFileSuite)();
|
||||
if (suite) {
|
||||
if (typeof modifierArgs[0] === "string" && typeof modifierArgs[1] === "function" && (type === "skip" || type === "fixme" || type === "fail")) {
|
||||
this._createTest(type, location, modifierArgs[0], modifierArgs[1]);
|
||||
return;
|
||||
}
|
||||
if (typeof modifierArgs[0] === "string" && typeof modifierArgs[1] === "object" && typeof modifierArgs[2] === "function" && (type === "skip" || type === "fixme" || type === "fail")) {
|
||||
this._createTest(type, location, modifierArgs[0], modifierArgs[1], modifierArgs[2]);
|
||||
return;
|
||||
}
|
||||
if (typeof modifierArgs[0] === "function") {
|
||||
suite._modifiers.push({ type, fn: modifierArgs[0], location, description: modifierArgs[1] });
|
||||
} else {
|
||||
if (modifierArgs.length >= 1 && !modifierArgs[0])
|
||||
return;
|
||||
const description = modifierArgs[1];
|
||||
suite._staticAnnotations.push({ type, description, location });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`test.${type}() can only be called inside test, describe block or fixture`);
|
||||
if (typeof modifierArgs[0] === "function")
|
||||
throw new Error(`test.${type}() with a function can only be called inside describe block`);
|
||||
testInfo._modifier(type, location, modifierArgs);
|
||||
}
|
||||
_setTimeout(location, timeout) {
|
||||
const suite = (0, import_globals.currentlyLoadingFileSuite)();
|
||||
if (suite) {
|
||||
suite._timeout = timeout;
|
||||
return;
|
||||
}
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`test.setTimeout() can only be called from a test`);
|
||||
testInfo.setTimeout(timeout);
|
||||
}
|
||||
_use(location, fixtures) {
|
||||
const suite = this._currentSuite(location, `test.use()`);
|
||||
if (!suite)
|
||||
return;
|
||||
suite._use.push({ fixtures, location });
|
||||
}
|
||||
async _step(expectation, title, body, options = {}) {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`test.step() can only be called from a test`);
|
||||
await testInfo._onUserStepBegin?.(title);
|
||||
const step = testInfo._addStep({ category: "test.step", title, location: options.location, box: options.box });
|
||||
return await (0, import_utils.currentZone)().with("stepZone", step).run(async () => {
|
||||
try {
|
||||
let result = void 0;
|
||||
result = await (0, import_utils.raceAgainstDeadline)(async () => {
|
||||
try {
|
||||
return await step.info._runStepBody(expectation === "skip", body, step.location);
|
||||
} catch (e) {
|
||||
if (result?.timedOut)
|
||||
testInfo._failWithError(e);
|
||||
throw e;
|
||||
}
|
||||
}, options.timeout ? (0, import_utils.monotonicTime)() + options.timeout : 0);
|
||||
if (result.timedOut)
|
||||
throw new import_playwright_core.errors.TimeoutError(`Step timeout of ${options.timeout}ms exceeded.`);
|
||||
step.complete({});
|
||||
return result.result;
|
||||
} catch (error) {
|
||||
step.complete({ error });
|
||||
throw error;
|
||||
} finally {
|
||||
await testInfo._onUserStepEnd?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
_extend(location, fixtures) {
|
||||
if (fixtures[testTypeSymbol])
|
||||
throw new Error(`test.extend() accepts fixtures object, not a test object.
|
||||
Did you mean to call mergeTests()?`);
|
||||
const fixturesWithLocation = { fixtures, location };
|
||||
return new TestTypeImpl([...this.fixtures, fixturesWithLocation]).test;
|
||||
}
|
||||
}
|
||||
function throwIfRunningInsideJest() {
|
||||
if (process.env.JEST_WORKER_ID) {
|
||||
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
||||
throw new Error(
|
||||
`Playwright Test needs to be invoked via '${packageManagerCommand} playwright test' and excluded from Jest test runs.
|
||||
Creating one directory for Playwright tests and one for Jest is the recommended way of doing it.
|
||||
See https://playwright.dev/docs/intro for more information about Playwright Test.`
|
||||
);
|
||||
}
|
||||
}
|
||||
const rootTestType = new TestTypeImpl([]);
|
||||
function mergeTests(...tests) {
|
||||
let result = rootTestType;
|
||||
for (const t of tests) {
|
||||
const testTypeImpl = t[testTypeSymbol];
|
||||
if (!testTypeImpl)
|
||||
throw new Error(`mergeTests() accepts "test" functions as parameters.
|
||||
Did you mean to call test.extend() with fixtures instead?`);
|
||||
const newFixtures = testTypeImpl.fixtures.filter((theirs) => !result.fixtures.find((ours) => ours.fixtures === theirs.fixtures));
|
||||
result = new TestTypeImpl([...result.fixtures, ...newFixtures]);
|
||||
}
|
||||
return result.test;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TestTypeImpl,
|
||||
mergeTests,
|
||||
rootTestType
|
||||
});
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var validators_exports = {};
|
||||
__export(validators_exports, {
|
||||
validateTestDetails: () => validateTestDetails
|
||||
});
|
||||
module.exports = __toCommonJS(validators_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
const testAnnotationSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
description: { type: "string" }
|
||||
},
|
||||
required: ["type"]
|
||||
};
|
||||
const testDetailsSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
tag: {
|
||||
oneOf: [
|
||||
{ type: "string", pattern: "^@", patternError: "Tag must start with '@'" },
|
||||
{ type: "array", items: { type: "string", pattern: "^@", patternError: "Tag must start with '@'" } }
|
||||
]
|
||||
},
|
||||
annotation: {
|
||||
oneOf: [
|
||||
testAnnotationSchema,
|
||||
{ type: "array", items: testAnnotationSchema }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
function validateTestDetails(details, location) {
|
||||
const errors = (0, import_utils.validate)(details, testDetailsSchema, "details");
|
||||
if (errors.length)
|
||||
throw new Error(errors.join("\n"));
|
||||
const obj = details;
|
||||
const tag = obj.tag;
|
||||
const tags = tag === void 0 ? [] : typeof tag === "string" ? [tag] : tag;
|
||||
const annotation = obj.annotation;
|
||||
const annotations = annotation === void 0 ? [] : Array.isArray(annotation) ? annotation : [annotation];
|
||||
return {
|
||||
annotations: annotations.map((a) => ({ ...a, location })),
|
||||
tags,
|
||||
location
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
validateTestDetails
|
||||
});
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var errorContext_exports = {};
|
||||
__export(errorContext_exports, {
|
||||
buildErrorContext: () => buildErrorContext
|
||||
});
|
||||
module.exports = __toCommonJS(errorContext_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("./util");
|
||||
const fixTestInstructions = `# Instructions
|
||||
|
||||
- Following Playwright test failed.
|
||||
- Explain why, be concise, respect Playwright best practices.
|
||||
- Provide a snippet of code with the fix, if possible.
|
||||
`;
|
||||
function buildErrorContext(options) {
|
||||
const { titlePath, location, errors, pageSnapshot } = options;
|
||||
const meaningfulErrors = errors.filter((e) => !!e.message);
|
||||
if (!meaningfulErrors.length && !pageSnapshot)
|
||||
return void 0;
|
||||
const lines = [
|
||||
fixTestInstructions,
|
||||
"# Test info",
|
||||
"",
|
||||
`- Name: ${titlePath.join(" >> ")}`,
|
||||
`- Location: ${(0, import_util.relativeFilePath)(location.file)}:${location.line}:${location.column}`
|
||||
];
|
||||
if (meaningfulErrors.length) {
|
||||
lines.push("", "# Error details");
|
||||
for (const error of meaningfulErrors) {
|
||||
lines.push(
|
||||
"",
|
||||
"```",
|
||||
(0, import_utils.stripAnsiEscapes)(error.message || ""),
|
||||
"```"
|
||||
);
|
||||
}
|
||||
}
|
||||
if (pageSnapshot) {
|
||||
lines.push(
|
||||
"",
|
||||
"# Page snapshot",
|
||||
"",
|
||||
"```yaml",
|
||||
pageSnapshot,
|
||||
"```"
|
||||
);
|
||||
}
|
||||
const lastError = meaningfulErrors[meaningfulErrors.length - 1];
|
||||
const codeFrame = lastError ? buildCodeFrame(lastError, location) : void 0;
|
||||
if (codeFrame) {
|
||||
lines.push(
|
||||
"",
|
||||
"# Test source",
|
||||
"",
|
||||
"```ts",
|
||||
codeFrame,
|
||||
"```"
|
||||
);
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
function buildCodeFrame(error, testLocation) {
|
||||
const stack = error.stack;
|
||||
if (!stack)
|
||||
return void 0;
|
||||
const parsed = (0, import_utils.parseErrorStack)(stack, import_path.default.sep);
|
||||
const errorLocation = parsed.location;
|
||||
if (!errorLocation)
|
||||
return void 0;
|
||||
let source;
|
||||
try {
|
||||
source = import_fs.default.readFileSync(errorLocation.file, "utf8");
|
||||
} catch {
|
||||
return void 0;
|
||||
}
|
||||
const sourceLines = source.split("\n");
|
||||
const linesAbove = 100;
|
||||
const linesBelow = 100;
|
||||
const start = Math.max(0, errorLocation.line - linesAbove - 1);
|
||||
const end = Math.min(sourceLines.length, errorLocation.line + linesBelow);
|
||||
const scope = sourceLines.slice(start, end);
|
||||
const lineNumberWidth = String(end).length;
|
||||
const message = (0, import_utils.stripAnsiEscapes)(error.message || "").split("\n")[0] || void 0;
|
||||
const frame = scope.map((line, index) => `${start + index + 1 === errorLocation.line ? "> " : " "}${(start + index + 1).toString().padEnd(lineNumberWidth, " ")} | ${line}`);
|
||||
if (message)
|
||||
frame.splice(errorLocation.line - start, 0, `${" ".repeat(lineNumberWidth + 2)} | ${" ".repeat(Math.max(0, errorLocation.column - 2))} ^ ${message}`);
|
||||
return frame.join("\n");
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
buildErrorContext
|
||||
});
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var fsWatcher_exports = {};
|
||||
__export(fsWatcher_exports, {
|
||||
Watcher: () => Watcher
|
||||
});
|
||||
module.exports = __toCommonJS(fsWatcher_exports);
|
||||
var import_utilsBundle = require("./utilsBundle");
|
||||
class Watcher {
|
||||
constructor(onChange) {
|
||||
this._watchedPaths = [];
|
||||
this._ignoredFolders = [];
|
||||
this._collector = [];
|
||||
this._onChange = onChange;
|
||||
}
|
||||
async update(watchedPaths, ignoredFolders, reportPending) {
|
||||
if (JSON.stringify([this._watchedPaths, this._ignoredFolders]) === JSON.stringify([watchedPaths, ignoredFolders]))
|
||||
return;
|
||||
if (reportPending)
|
||||
this._reportEventsIfAny();
|
||||
this._watchedPaths = watchedPaths;
|
||||
this._ignoredFolders = ignoredFolders;
|
||||
void this._fsWatcher?.close();
|
||||
this._fsWatcher = void 0;
|
||||
this._collector.length = 0;
|
||||
clearTimeout(this._throttleTimer);
|
||||
this._throttleTimer = void 0;
|
||||
if (!this._watchedPaths.length)
|
||||
return;
|
||||
const ignored = [...this._ignoredFolders, "**/node_modules/**"];
|
||||
this._fsWatcher = import_utilsBundle.chokidar.watch(watchedPaths, { ignoreInitial: true, ignored }).on("all", async (event, file) => {
|
||||
if (this._throttleTimer)
|
||||
clearTimeout(this._throttleTimer);
|
||||
this._collector.push({ event, file });
|
||||
this._throttleTimer = setTimeout(() => this._reportEventsIfAny(), 250);
|
||||
});
|
||||
await new Promise((resolve, reject) => this._fsWatcher.once("ready", resolve).once("error", reject));
|
||||
}
|
||||
async close() {
|
||||
await this._fsWatcher?.close();
|
||||
}
|
||||
_reportEventsIfAny() {
|
||||
if (this._collector.length)
|
||||
this._onChange(this._collector.slice());
|
||||
this._collector.length = 0;
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Watcher
|
||||
});
|
||||
+764
@@ -0,0 +1,764 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var index_exports = {};
|
||||
__export(index_exports, {
|
||||
_baseTest: () => _baseTest,
|
||||
defineConfig: () => import_configLoader.defineConfig,
|
||||
expect: () => import_expect.expect,
|
||||
mergeExpects: () => import_expect2.mergeExpects,
|
||||
mergeTests: () => import_testType2.mergeTests,
|
||||
test: () => test
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var playwrightLibrary = __toESM(require("playwright-core"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_errorContext = require("./errorContext");
|
||||
var import_globals = require("./common/globals");
|
||||
var import_testType = require("./common/testType");
|
||||
var import_browserBackend = require("./mcp/test/browserBackend");
|
||||
var import_expect = require("./matchers/expect");
|
||||
var import_configLoader = require("./common/configLoader");
|
||||
var import_testType2 = require("./common/testType");
|
||||
var import_expect2 = require("./matchers/expect");
|
||||
const _baseTest = import_testType.rootTestType.test;
|
||||
(0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
|
||||
if (process["__pw_initiator__"]) {
|
||||
const originalStackTraceLimit = Error.stackTraceLimit;
|
||||
Error.stackTraceLimit = 200;
|
||||
try {
|
||||
throw new Error("Requiring @playwright/test second time, \nFirst:\n" + process["__pw_initiator__"] + "\n\nSecond: ");
|
||||
} finally {
|
||||
Error.stackTraceLimit = originalStackTraceLimit;
|
||||
}
|
||||
} else {
|
||||
process["__pw_initiator__"] = new Error().stack;
|
||||
}
|
||||
const playwrightFixtures = {
|
||||
defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
|
||||
browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
|
||||
playwright: [async ({}, use) => {
|
||||
await use(require("playwright-core"));
|
||||
}, { scope: "worker", box: true }],
|
||||
headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
|
||||
channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
|
||||
launchOptions: [{}, { scope: "worker", option: true, box: true }],
|
||||
connectOptions: [async ({ _optionConnectOptions }, use) => {
|
||||
await use(connectOptionsFromEnv() || _optionConnectOptions);
|
||||
}, { scope: "worker", option: true, box: true }],
|
||||
screenshot: ["off", { scope: "worker", option: true, box: true }],
|
||||
video: ["off", { scope: "worker", option: true, box: true }],
|
||||
trace: ["off", { scope: "worker", option: true, box: true }],
|
||||
_browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
|
||||
const options = {
|
||||
handleSIGINT: false,
|
||||
...launchOptions,
|
||||
tracesDir: tracing().tracesDir(),
|
||||
artifactsDir: tracing().artifactsDir()
|
||||
};
|
||||
if (headless !== void 0)
|
||||
options.headless = headless;
|
||||
if (channel !== void 0)
|
||||
options.channel = channel;
|
||||
playwright._defaultLaunchOptions = options;
|
||||
await use(options);
|
||||
playwright._defaultLaunchOptions = void 0;
|
||||
}, { scope: "worker", auto: true, box: true }],
|
||||
browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, workerInfo) => {
|
||||
if (!["chromium", "firefox", "webkit"].includes(browserName))
|
||||
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
||||
if (connectOptions) {
|
||||
const browser2 = await playwright[browserName].connect(connectOptions.wsEndpoint, {
|
||||
...connectOptions,
|
||||
exposeNetwork: connectOptions.exposeNetwork,
|
||||
headers: {
|
||||
// HTTP headers are ASCII only (not UTF-8).
|
||||
"x-playwright-launch-options": (0, import_utils.jsonStringifyForceASCII)(_browserOptions),
|
||||
...connectOptions.headers
|
||||
}
|
||||
});
|
||||
await use(browser2);
|
||||
await browser2.close({ reason: "Test ended." });
|
||||
return;
|
||||
}
|
||||
const browser = await playwright[browserName].launch();
|
||||
if (process.env.PLAYWRIGHT_DASHBOARD)
|
||||
await browser.bind(`worker-${workerInfo.parallelIndex}`);
|
||||
await use(browser);
|
||||
await browser.close({ reason: "Test ended." });
|
||||
}, { scope: "worker", timeout: 0 }],
|
||||
acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
|
||||
bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
|
||||
colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
|
||||
deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
|
||||
extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true, box: true }],
|
||||
geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true, box: true }],
|
||||
hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true, box: true }],
|
||||
httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true, box: true }],
|
||||
ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true, box: true }],
|
||||
isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true, box: true }],
|
||||
javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true, box: true }],
|
||||
locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true, box: true }],
|
||||
offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true, box: true }],
|
||||
permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true, box: true }],
|
||||
proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true, box: true }],
|
||||
storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true, box: true }],
|
||||
clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true, box: true }],
|
||||
timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true, box: true }],
|
||||
userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
|
||||
viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true, box: true }],
|
||||
actionTimeout: [0, { option: true, box: true }],
|
||||
testIdAttribute: ["data-testid", { option: true, box: true }],
|
||||
navigationTimeout: [0, { option: true, box: true }],
|
||||
baseURL: [async ({}, use) => {
|
||||
await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
|
||||
}, { option: true, box: true }],
|
||||
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
|
||||
contextOptions: [{}, { option: true, box: true }],
|
||||
_combinedContextOptions: [async ({
|
||||
acceptDownloads,
|
||||
bypassCSP,
|
||||
clientCertificates,
|
||||
colorScheme,
|
||||
deviceScaleFactor,
|
||||
extraHTTPHeaders,
|
||||
hasTouch,
|
||||
geolocation,
|
||||
httpCredentials,
|
||||
ignoreHTTPSErrors,
|
||||
isMobile,
|
||||
javaScriptEnabled,
|
||||
locale,
|
||||
offline,
|
||||
permissions,
|
||||
proxy,
|
||||
storageState,
|
||||
viewport,
|
||||
timezoneId,
|
||||
userAgent,
|
||||
baseURL,
|
||||
contextOptions,
|
||||
serviceWorkers
|
||||
}, use, testInfo) => {
|
||||
const options = {};
|
||||
if (acceptDownloads !== void 0)
|
||||
options.acceptDownloads = acceptDownloads;
|
||||
if (bypassCSP !== void 0)
|
||||
options.bypassCSP = bypassCSP;
|
||||
if (colorScheme !== void 0)
|
||||
options.colorScheme = colorScheme;
|
||||
if (deviceScaleFactor !== void 0)
|
||||
options.deviceScaleFactor = deviceScaleFactor;
|
||||
if (extraHTTPHeaders !== void 0)
|
||||
options.extraHTTPHeaders = extraHTTPHeaders;
|
||||
if (geolocation !== void 0)
|
||||
options.geolocation = geolocation;
|
||||
if (hasTouch !== void 0)
|
||||
options.hasTouch = hasTouch;
|
||||
if (httpCredentials !== void 0)
|
||||
options.httpCredentials = httpCredentials;
|
||||
if (ignoreHTTPSErrors !== void 0)
|
||||
options.ignoreHTTPSErrors = ignoreHTTPSErrors;
|
||||
if (isMobile !== void 0)
|
||||
options.isMobile = isMobile;
|
||||
if (javaScriptEnabled !== void 0)
|
||||
options.javaScriptEnabled = javaScriptEnabled;
|
||||
if (locale !== void 0)
|
||||
options.locale = locale;
|
||||
if (offline !== void 0)
|
||||
options.offline = offline;
|
||||
if (permissions !== void 0)
|
||||
options.permissions = permissions;
|
||||
if (proxy !== void 0)
|
||||
options.proxy = proxy;
|
||||
if (storageState !== void 0)
|
||||
options.storageState = storageState;
|
||||
if (clientCertificates?.length)
|
||||
options.clientCertificates = resolveClientCerticates(clientCertificates);
|
||||
if (timezoneId !== void 0)
|
||||
options.timezoneId = timezoneId;
|
||||
if (userAgent !== void 0)
|
||||
options.userAgent = userAgent;
|
||||
if (viewport !== void 0)
|
||||
options.viewport = viewport;
|
||||
if (baseURL !== void 0)
|
||||
options.baseURL = baseURL;
|
||||
if (serviceWorkers !== void 0)
|
||||
options.serviceWorkers = serviceWorkers;
|
||||
await use({
|
||||
...contextOptions,
|
||||
...options
|
||||
});
|
||||
}, { box: true }],
|
||||
_setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, _testInfo) => {
|
||||
const testInfo = _testInfo;
|
||||
if (testIdAttribute)
|
||||
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
||||
testInfo.snapshotSuffix = process.platform;
|
||||
testInfo._onCustomMessageCallback = () => Promise.reject(new Error("Only tests that use default Playwright context or page fixture support test_debug"));
|
||||
if ((0, import_utils.debugMode)() === "inspector")
|
||||
testInfo._setIgnoreTimeouts(true);
|
||||
playwright._defaultContextTimeout = actionTimeout || 0;
|
||||
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
||||
await use();
|
||||
playwright._defaultContextTimeout = void 0;
|
||||
playwright._defaultContextNavigationTimeout = void 0;
|
||||
}, { auto: "all-hooks-included", title: "context configuration", box: true }],
|
||||
_setupArtifacts: [async ({ playwright, screenshot, _combinedContextOptions }, use, testInfo) => {
|
||||
testInfo.setTimeout(testInfo.project.timeout);
|
||||
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
|
||||
await artifactsRecorder.willStartTest(testInfo);
|
||||
const tracingGroupSteps = [];
|
||||
const pausedContexts = /* @__PURE__ */ new Set();
|
||||
const csiListener = {
|
||||
onApiCallBegin: (data, channel) => {
|
||||
const testInfo2 = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
|
||||
return;
|
||||
const zone = (0, import_utils.currentZone)().data("stepZone");
|
||||
const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
|
||||
if (zone && zone.category === "expect" && isExpectCall) {
|
||||
if (zone.apiName)
|
||||
data.apiName = zone.apiName;
|
||||
if (zone.shortTitle || zone.title)
|
||||
data.title = zone.shortTitle ?? zone.title;
|
||||
data.stepId = zone.stepId;
|
||||
return;
|
||||
}
|
||||
const step = testInfo2._addStep({
|
||||
location: data.frames[0],
|
||||
category: "pw:api",
|
||||
title: renderTitle(channel.type, channel.method, channel.params, data.title),
|
||||
apiName: data.apiName,
|
||||
params: channel.params,
|
||||
group: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method })
|
||||
}, tracingGroupSteps[tracingGroupSteps.length - 1]);
|
||||
data.userData = step;
|
||||
data.stepId = step.stepId;
|
||||
if (data.apiName === "tracing.group")
|
||||
tracingGroupSteps.push(step);
|
||||
},
|
||||
onApiCallEnd: (data) => {
|
||||
if (data.apiName === "tracing.group")
|
||||
return;
|
||||
if (data.apiName === "tracing.groupEnd") {
|
||||
const step2 = tracingGroupSteps.pop();
|
||||
step2?.complete({ error: data.error });
|
||||
return;
|
||||
}
|
||||
const step = data.userData;
|
||||
step?.complete({ error: data.error });
|
||||
},
|
||||
onWillPause: ({ keepTestTimeout }) => {
|
||||
if (!keepTestTimeout)
|
||||
(0, import_globals.currentTestInfo)()?._setIgnoreTimeouts(true);
|
||||
},
|
||||
runBeforeCreateBrowserContext: async (options) => {
|
||||
for (const [key, value] of Object.entries(_combinedContextOptions)) {
|
||||
if (!(key in options))
|
||||
options[key] = value;
|
||||
}
|
||||
},
|
||||
runBeforeCreateRequestContext: async (options) => {
|
||||
for (const [key, value] of Object.entries(_combinedContextOptions)) {
|
||||
if (!(key in options))
|
||||
options[key] = value;
|
||||
}
|
||||
},
|
||||
runAfterCreateBrowserContext: async (context) => {
|
||||
context.debugger.on("pausedstatechanged", () => {
|
||||
const paused = !!context.debugger.pausedDetails();
|
||||
if (pausedContexts.has(context) && !paused) {
|
||||
pausedContexts.delete(context);
|
||||
testInfo2._setIgnoreTimeouts(false);
|
||||
} else if (!pausedContexts.has(context) && paused) {
|
||||
pausedContexts.add(context);
|
||||
testInfo2._setIgnoreTimeouts(true);
|
||||
}
|
||||
});
|
||||
await artifactsRecorder.didCreateBrowserContext(context);
|
||||
const testInfo2 = (0, import_globals.currentTestInfo)();
|
||||
if (testInfo2)
|
||||
attachConnectedHeaderIfNeeded(testInfo2, context.browser());
|
||||
},
|
||||
runAfterCreateRequestContext: async (context) => {
|
||||
await artifactsRecorder.didCreateRequestContext(context);
|
||||
},
|
||||
runBeforeCloseBrowserContext: async (context) => {
|
||||
await artifactsRecorder.willCloseBrowserContext(context);
|
||||
},
|
||||
runBeforeCloseRequestContext: async (context) => {
|
||||
await artifactsRecorder.willCloseRequestContext(context);
|
||||
}
|
||||
};
|
||||
const clientInstrumentation = playwright._instrumentation;
|
||||
clientInstrumentation.addListener(csiListener);
|
||||
await use();
|
||||
clientInstrumentation.removeListener(csiListener);
|
||||
await artifactsRecorder.didFinishTest();
|
||||
}, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }],
|
||||
_contextFactory: [async ({
|
||||
browser,
|
||||
video,
|
||||
_reuseContext,
|
||||
_combinedContextOptions
|
||||
/** mitigate dep-via-auto lack of traceability */
|
||||
}, use, testInfo) => {
|
||||
const testInfoImpl = testInfo;
|
||||
const videoMode = normalizeVideoMode(video);
|
||||
const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
|
||||
const contexts = /* @__PURE__ */ new Map();
|
||||
let counter = 0;
|
||||
await use(async (options) => {
|
||||
const hook = testInfoImpl._currentHookType();
|
||||
if (hook === "beforeAll" || hook === "afterAll") {
|
||||
throw new Error([
|
||||
`"context" and "page" fixtures are not supported in "${hook}" since they are created on a per-test basis.`,
|
||||
`If you would like to reuse a single page between tests, create context manually with browser.newContext(). See https://aka.ms/playwright/reuse-page for details.`,
|
||||
`If you would like to configure your page before each test, do that in beforeEach hook instead.`
|
||||
].join("\n"));
|
||||
}
|
||||
const show = typeof video === "string" ? void 0 : video.show;
|
||||
const videoOptions = captureVideo ? {
|
||||
recordVideo: {
|
||||
dir: tracing().artifactsDir(),
|
||||
size: typeof video === "string" ? void 0 : video.size,
|
||||
showActions: show?.actions
|
||||
}
|
||||
} : {};
|
||||
const context = await browser.newContext({ ...videoOptions, ...options });
|
||||
if (process.env.PW_CLOCK === "frozen") {
|
||||
await context._wrapApiCall(async () => {
|
||||
await context.clock.install({ time: 0 });
|
||||
await context.clock.pauseAt(1e3);
|
||||
}, { internal: true });
|
||||
} else if (process.env.PW_CLOCK === "realtime") {
|
||||
await context._wrapApiCall(async () => {
|
||||
await context.clock.install({ time: 0 });
|
||||
}, { internal: true });
|
||||
}
|
||||
let closed = false;
|
||||
const close = async () => {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
||||
await context.close({ reason: closeReason });
|
||||
const testFailed = testInfo.status !== testInfo.expectedStatus;
|
||||
const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
|
||||
if (preserveVideo) {
|
||||
const { pagesWithVideo: pagesForVideo } = contexts.get(context);
|
||||
const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
|
||||
await Promise.all(videos.map(async (v) => {
|
||||
try {
|
||||
const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
|
||||
++counter;
|
||||
await v.saveAs(savedPath);
|
||||
testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
|
||||
} catch (e) {
|
||||
}
|
||||
}));
|
||||
}
|
||||
};
|
||||
const contextData = { close, pagesWithVideo: [] };
|
||||
if (captureVideo)
|
||||
context.on("page", (page) => contextData.pagesWithVideo.push(page));
|
||||
contexts.set(context, contextData);
|
||||
return { context, close };
|
||||
});
|
||||
await Promise.all([...contexts.values()].map((data) => data.close()));
|
||||
}, { scope: "test", title: "context", box: true }],
|
||||
_optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
|
||||
_optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
|
||||
_reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
|
||||
let mode = _optionContextReuseMode;
|
||||
if (process.env.PW_TEST_REUSE_CONTEXT)
|
||||
mode = "when-possible";
|
||||
const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
|
||||
await use(reuse);
|
||||
}, { scope: "worker", title: "context", box: true }],
|
||||
context: async ({ browser, video, _reuseContext, _contextFactory }, use, testInfoPublic) => {
|
||||
const browserImpl = browser;
|
||||
const testInfo = testInfoPublic;
|
||||
const show = typeof video === "string" ? void 0 : video.show;
|
||||
attachConnectedHeaderIfNeeded(testInfo, browserImpl);
|
||||
if (!_reuseContext) {
|
||||
const { context: context2, close } = await _contextFactory();
|
||||
testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context2);
|
||||
await (0, import_browserBackend.runDaemonForContext)(testInfo, context2);
|
||||
await installScreencastTitleUpdater(testInfo, context2, show?.test);
|
||||
await use(context2);
|
||||
await close();
|
||||
return;
|
||||
}
|
||||
const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
|
||||
testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context);
|
||||
await (0, import_browserBackend.runDaemonForContext)(testInfo, context);
|
||||
await installScreencastTitleUpdater(testInfo, context, show?.test);
|
||||
await use(context);
|
||||
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
||||
await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
|
||||
},
|
||||
page: async ({ context, _reuseContext }, use) => {
|
||||
if (!_reuseContext) {
|
||||
await use(await context.newPage());
|
||||
return;
|
||||
}
|
||||
let [page] = context.pages();
|
||||
if (!page)
|
||||
page = await context.newPage();
|
||||
await use(page);
|
||||
},
|
||||
request: async ({ playwright }, use) => {
|
||||
const request = await playwright.request.newContext();
|
||||
await use(request);
|
||||
const hook = test.info()._currentHookType();
|
||||
if (hook === "beforeAll") {
|
||||
await request.dispose({ reason: [
|
||||
`Fixture { request } from beforeAll cannot be reused in a test.`,
|
||||
` - Recommended fix: use a separate { request } in the test.`,
|
||||
` - Alternatively, manually create APIRequestContext in beforeAll and dispose it in afterAll.`,
|
||||
`See https://playwright.dev/docs/api-testing#sending-api-requests-from-ui-tests for more details.`
|
||||
].join("\n") });
|
||||
} else {
|
||||
await request.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
function normalizeVideoMode(video) {
|
||||
if (!video)
|
||||
return "off";
|
||||
let videoMode = typeof video === "string" ? video : video.mode;
|
||||
if (videoMode === "retry-with-video")
|
||||
videoMode = "on-first-retry";
|
||||
return videoMode;
|
||||
}
|
||||
function shouldCaptureVideo(videoMode, testInfo) {
|
||||
return videoMode === "on" || videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1;
|
||||
}
|
||||
function normalizeScreenshotMode(screenshot) {
|
||||
if (!screenshot)
|
||||
return "off";
|
||||
return typeof screenshot === "string" ? screenshot : screenshot.mode;
|
||||
}
|
||||
function attachConnectedHeaderIfNeeded(testInfo, browser) {
|
||||
const connectHeaders = browser?._connection.headers;
|
||||
if (!connectHeaders)
|
||||
return;
|
||||
for (const header of connectHeaders) {
|
||||
if (header.name !== "x-playwright-attachment")
|
||||
continue;
|
||||
const [name, value] = header.value.split("=");
|
||||
if (!name || !value)
|
||||
continue;
|
||||
if (testInfo.attachments.some((attachment) => attachment.name === name))
|
||||
continue;
|
||||
testInfo.attachments.push({ name, contentType: "text/plain", body: Buffer.from(value) });
|
||||
}
|
||||
}
|
||||
function resolveFileToConfig(file) {
|
||||
const config = test.info().config.configFile;
|
||||
if (!config || !file)
|
||||
return file;
|
||||
if (import_path.default.isAbsolute(file))
|
||||
return file;
|
||||
return import_path.default.resolve(import_path.default.dirname(config), file);
|
||||
}
|
||||
function resolveClientCerticates(clientCertificates) {
|
||||
for (const cert of clientCertificates) {
|
||||
cert.certPath = resolveFileToConfig(cert.certPath);
|
||||
cert.keyPath = resolveFileToConfig(cert.keyPath);
|
||||
cert.pfxPath = resolveFileToConfig(cert.pfxPath);
|
||||
}
|
||||
return clientCertificates;
|
||||
}
|
||||
const kTracingStarted = Symbol("kTracingStarted");
|
||||
function connectOptionsFromEnv() {
|
||||
const wsEndpoint = process.env.PW_TEST_CONNECT_WS_ENDPOINT;
|
||||
if (!wsEndpoint)
|
||||
return void 0;
|
||||
const headers = process.env.PW_TEST_CONNECT_HEADERS ? JSON.parse(process.env.PW_TEST_CONNECT_HEADERS) : void 0;
|
||||
return {
|
||||
wsEndpoint,
|
||||
headers,
|
||||
exposeNetwork: process.env.PW_TEST_CONNECT_EXPOSE_NETWORK
|
||||
};
|
||||
}
|
||||
class SnapshotRecorder {
|
||||
constructor(_artifactsRecorder, _mode, _name, _contentType, _extension, _doSnapshot) {
|
||||
this._artifactsRecorder = _artifactsRecorder;
|
||||
this._mode = _mode;
|
||||
this._name = _name;
|
||||
this._contentType = _contentType;
|
||||
this._extension = _extension;
|
||||
this._doSnapshot = _doSnapshot;
|
||||
this._ordinal = 0;
|
||||
this._temporary = [];
|
||||
}
|
||||
fixOrdinal() {
|
||||
this._ordinal = this.testInfo.attachments.filter((a) => a.name === this._name).length;
|
||||
}
|
||||
shouldCaptureUponFinish() {
|
||||
return this._mode === "on" || this._mode === "only-on-failure" && this.testInfo._isFailure() || this._mode === "on-first-failure" && this.testInfo._isFailure() && this.testInfo.retry === 0;
|
||||
}
|
||||
async maybeCapture() {
|
||||
if (!this.shouldCaptureUponFinish())
|
||||
return;
|
||||
await Promise.all(this._artifactsRecorder._playwright._allPages().map((page) => this._snapshotPage(page, false)));
|
||||
}
|
||||
async persistTemporary() {
|
||||
if (this.shouldCaptureUponFinish()) {
|
||||
await Promise.all(this._temporary.map(async (file) => {
|
||||
try {
|
||||
const path2 = this._createAttachmentPath();
|
||||
await import_fs.default.promises.rename(file, path2);
|
||||
this._attach(path2);
|
||||
} catch {
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
async captureTemporary(context) {
|
||||
if (this._mode === "on" || this._mode === "only-on-failure" || this._mode === "on-first-failure" && this.testInfo.retry === 0)
|
||||
await Promise.all(context.pages().map((page) => this._snapshotPage(page, true)));
|
||||
}
|
||||
_attach(screenshotPath) {
|
||||
this.testInfo.attachments.push({ name: this._name, path: screenshotPath, contentType: this._contentType });
|
||||
}
|
||||
_createAttachmentPath() {
|
||||
const testFailed = this.testInfo._isFailure();
|
||||
const index = this._ordinal + 1;
|
||||
++this._ordinal;
|
||||
const path2 = this.testInfo.outputPath(`test-${testFailed ? "failed" : "finished"}-${index}${this._extension}`);
|
||||
return path2;
|
||||
}
|
||||
_createTemporaryArtifact(...name) {
|
||||
const file = import_path.default.join(this._artifactsRecorder._artifactsDir, ...name);
|
||||
return file;
|
||||
}
|
||||
async _snapshotPage(page, temporary) {
|
||||
if (page[this.testInfo._uniqueSymbol])
|
||||
return;
|
||||
page[this.testInfo._uniqueSymbol] = true;
|
||||
try {
|
||||
const path2 = temporary ? this._createTemporaryArtifact((0, import_utils.createGuid)() + this._extension) : this._createAttachmentPath();
|
||||
await this._doSnapshot(page, path2);
|
||||
if (temporary)
|
||||
this._temporary.push(path2);
|
||||
else
|
||||
this._attach(path2);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
get testInfo() {
|
||||
return this._artifactsRecorder._testInfo;
|
||||
}
|
||||
}
|
||||
class ArtifactsRecorder {
|
||||
constructor(playwright, artifactsDir, screenshot) {
|
||||
this._playwright = playwright;
|
||||
this._artifactsDir = artifactsDir;
|
||||
const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
|
||||
this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
|
||||
this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
|
||||
await page._wrapApiCall(async () => {
|
||||
await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
|
||||
}, { internal: true });
|
||||
});
|
||||
}
|
||||
async willStartTest(testInfo) {
|
||||
this._testInfo = testInfo;
|
||||
testInfo._onDidFinishTestFunctionCallbacks.add(() => this.didFinishTestFunction());
|
||||
this._screenshotRecorder.fixOrdinal();
|
||||
await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
|
||||
const existingApiRequests = Array.from(this._playwright.request._contexts);
|
||||
await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c)));
|
||||
}
|
||||
async didCreateBrowserContext(context) {
|
||||
await this._startTraceChunkOnContextCreation(context, context.tracing);
|
||||
}
|
||||
async willCloseBrowserContext(context) {
|
||||
await this._stopTracing(context, context.tracing);
|
||||
await this._screenshotRecorder.captureTemporary(context);
|
||||
await this._takePageSnapshot(context);
|
||||
}
|
||||
async _takePageSnapshot(context) {
|
||||
if (process.env.PLAYWRIGHT_NO_COPY_PROMPT)
|
||||
return;
|
||||
if (this._testInfo.errors.length === 0)
|
||||
return;
|
||||
if (this._pageSnapshot)
|
||||
return;
|
||||
const page = context.pages()[0];
|
||||
if (!page)
|
||||
return;
|
||||
try {
|
||||
await page._wrapApiCall(async () => {
|
||||
this._pageSnapshot = await page.ariaSnapshot({ mode: "ai", timeout: 5e3 });
|
||||
}, { internal: true });
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
async didCreateRequestContext(context) {
|
||||
await this._startTraceChunkOnContextCreation(context, context._tracing);
|
||||
}
|
||||
async willCloseRequestContext(context) {
|
||||
await this._stopTracing(context, context._tracing);
|
||||
}
|
||||
async didFinishTestFunction() {
|
||||
await this._screenshotRecorder.maybeCapture();
|
||||
}
|
||||
async didFinishTest() {
|
||||
await this.didFinishTestFunction();
|
||||
const leftoverContexts = this._playwright._allContexts();
|
||||
const leftoverApiRequests = Array.from(this._playwright.request._contexts);
|
||||
await Promise.all(leftoverContexts.map(async (context2) => {
|
||||
await this._stopTracing(context2, context2.tracing);
|
||||
}).concat(leftoverApiRequests.map(async (context2) => {
|
||||
await this._stopTracing(context2, context2._tracing);
|
||||
})));
|
||||
await this._screenshotRecorder.persistTemporary();
|
||||
const context = leftoverContexts[0];
|
||||
if (context)
|
||||
await this._takePageSnapshot(context);
|
||||
if (this._testInfo.errors.length > 0) {
|
||||
const errorContextContent = (0, import_errorContext.buildErrorContext)({
|
||||
titlePath: this._testInfo.titlePath,
|
||||
location: { file: this._testInfo.file, line: this._testInfo.line, column: this._testInfo.column },
|
||||
errors: this._testInfo.errors,
|
||||
pageSnapshot: this._pageSnapshot
|
||||
});
|
||||
if (errorContextContent) {
|
||||
const filePath = this._testInfo.outputPath("error-context.md");
|
||||
await import_fs.default.promises.writeFile(filePath, errorContextContent, "utf8");
|
||||
this._testInfo._attach({
|
||||
name: "error-context",
|
||||
contentType: "text/markdown",
|
||||
path: filePath
|
||||
}, void 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
|
||||
await channelOwner._wrapApiCall(async () => {
|
||||
const options = this._testInfo._tracing.traceOptions();
|
||||
if (options) {
|
||||
const title = this._testInfo._tracing.traceTitle();
|
||||
const name = this._testInfo._tracing.generateNextTraceRecordingName();
|
||||
if (!tracing2[kTracingStarted]) {
|
||||
await tracing2.start({ ...options, title, name });
|
||||
tracing2[kTracingStarted] = true;
|
||||
} else {
|
||||
await tracing2.startChunk({ title, name });
|
||||
}
|
||||
} else {
|
||||
if (tracing2[kTracingStarted]) {
|
||||
tracing2[kTracingStarted] = false;
|
||||
await tracing2.stop();
|
||||
}
|
||||
}
|
||||
}, { internal: true });
|
||||
}
|
||||
async _stopTracing(channelOwner, tracing2) {
|
||||
await channelOwner._wrapApiCall(async () => {
|
||||
if (tracing2[this._startedCollectingArtifacts])
|
||||
return;
|
||||
tracing2[this._startedCollectingArtifacts] = true;
|
||||
if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
|
||||
await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
|
||||
}, { internal: true });
|
||||
}
|
||||
}
|
||||
async function installScreencastTitleUpdater(testInfo, context, testAnnotate) {
|
||||
if (!testAnnotate)
|
||||
return;
|
||||
const testTitle = testAnnotate.level === "file" ? [testInfo.titlePath[0]] : testInfo.titlePath;
|
||||
const stepStack = [];
|
||||
const overlays = /* @__PURE__ */ new Map();
|
||||
const position = testAnnotate.position ?? "top-left";
|
||||
const fontSize = testAnnotate.fontSize ?? 14;
|
||||
const level = testAnnotate.level ?? "step";
|
||||
const updateOverlay = async () => {
|
||||
const parts = level === "step" ? [...testTitle, ...stepStack] : testTitle;
|
||||
const html = createTestOverlay(parts, position, fontSize);
|
||||
for (const page of context.pages()) {
|
||||
await overlays.get(page)?.dispose();
|
||||
overlays.delete(page);
|
||||
const disposable = await page.screencast.showOverlay(html);
|
||||
overlays.set(page, disposable);
|
||||
}
|
||||
};
|
||||
testInfo._onUserStepBegin = async (title) => {
|
||||
stepStack.push(title);
|
||||
await updateOverlay();
|
||||
};
|
||||
testInfo._onUserStepEnd = async () => {
|
||||
stepStack.pop();
|
||||
await updateOverlay();
|
||||
};
|
||||
context.on("page", async () => {
|
||||
void updateOverlay();
|
||||
});
|
||||
await updateOverlay();
|
||||
}
|
||||
function createTestOverlay(parts, position, fontSize) {
|
||||
const positionStyles = {
|
||||
"top-left": "top: 6px; left: 6px;",
|
||||
"top": "top: 6px; left: 50%; transform: translateX(-50%);",
|
||||
"top-right": "top: 6px; right: 6px;",
|
||||
"bottom-left": "bottom: 6px; left: 6px;",
|
||||
"bottom": "bottom: 6px; left: 50%; transform: translateX(-50%);",
|
||||
"bottom-right": "bottom: 6px; right: 6px;"
|
||||
};
|
||||
const posStyle = positionStyles[position] ?? positionStyles["top-left"];
|
||||
return `<div style="white-space: nowrap; font-size: ${fontSize}px; padding: 3px 6px; background: rgba(0,0,0,0.5); color: white; border-radius: 4px; position: absolute; ${posStyle}">
|
||||
${parts.map((p) => `<div>${(0, import_utils.escapeHTML)(p)}</div>`).join("")}
|
||||
</div>`;
|
||||
}
|
||||
function renderTitle(type, method, params, title) {
|
||||
const prefix = (0, import_utils.renderTitleForCall)({ title, type, method, params });
|
||||
let selector;
|
||||
if (params?.["selector"] && typeof params.selector === "string")
|
||||
selector = (0, import_utils.asLocatorDescription)("javascript", params.selector);
|
||||
return prefix + (selector ? ` ${selector}` : "");
|
||||
}
|
||||
function tracing() {
|
||||
return test.info()._tracing;
|
||||
}
|
||||
const test = _baseTest.extend(playwrightFixtures);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
_baseTest,
|
||||
defineConfig,
|
||||
expect,
|
||||
mergeExpects,
|
||||
mergeTests,
|
||||
test
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var internalsForTest_exports = {};
|
||||
__export(internalsForTest_exports, {
|
||||
fileDependencies: () => fileDependencies
|
||||
});
|
||||
module.exports = __toCommonJS(internalsForTest_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_compilationCache = require("./transform/compilationCache");
|
||||
function fileDependencies() {
|
||||
return Object.fromEntries([...(0, import_compilationCache.fileDependenciesForTest)().entries()].map((entry) => [import_path.default.basename(entry[0]), [...entry[1]].map((f) => import_path.default.basename(f)).sort()]));
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
fileDependencies
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var events_exports = {};
|
||||
__export(events_exports, {
|
||||
Disposable: () => Disposable,
|
||||
EventEmitter: () => EventEmitter
|
||||
});
|
||||
module.exports = __toCommonJS(events_exports);
|
||||
var Disposable;
|
||||
((Disposable2) => {
|
||||
function disposeAll(disposables) {
|
||||
for (const disposable of disposables.splice(0))
|
||||
disposable.dispose();
|
||||
}
|
||||
Disposable2.disposeAll = disposeAll;
|
||||
})(Disposable || (Disposable = {}));
|
||||
class EventEmitter {
|
||||
constructor() {
|
||||
this._listeners = /* @__PURE__ */ new Set();
|
||||
this.event = (listener, disposables) => {
|
||||
this._listeners.add(listener);
|
||||
let disposed = false;
|
||||
const self = this;
|
||||
const result = {
|
||||
dispose() {
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
self._listeners.delete(listener);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (disposables)
|
||||
disposables.push(result);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
fire(event) {
|
||||
const dispatch = !this._deliveryQueue;
|
||||
if (!this._deliveryQueue)
|
||||
this._deliveryQueue = [];
|
||||
for (const listener of this._listeners)
|
||||
this._deliveryQueue.push({ listener, event });
|
||||
if (!dispatch)
|
||||
return;
|
||||
for (let index = 0; index < this._deliveryQueue.length; index++) {
|
||||
const { listener, event: event2 } = this._deliveryQueue[index];
|
||||
listener.call(null, event2);
|
||||
}
|
||||
this._deliveryQueue = void 0;
|
||||
}
|
||||
dispose() {
|
||||
this._listeners.clear();
|
||||
if (this._deliveryQueue)
|
||||
this._deliveryQueue = [];
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Disposable,
|
||||
EventEmitter
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var folders_exports = {};
|
||||
__export(folders_exports, {
|
||||
artifactsFolderName: () => artifactsFolderName
|
||||
});
|
||||
module.exports = __toCommonJS(folders_exports);
|
||||
function artifactsFolderName(workerIndex) {
|
||||
return `.playwright-artifacts-${workerIndex}`;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
artifactsFolderName
|
||||
});
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var stringInternPool_exports = {};
|
||||
__export(stringInternPool_exports, {
|
||||
JsonStringInternalizer: () => JsonStringInternalizer,
|
||||
StringInternPool: () => StringInternPool
|
||||
});
|
||||
module.exports = __toCommonJS(stringInternPool_exports);
|
||||
class StringInternPool {
|
||||
constructor() {
|
||||
this._stringCache = /* @__PURE__ */ new Map();
|
||||
}
|
||||
internString(s) {
|
||||
let result = this._stringCache.get(s);
|
||||
if (!result) {
|
||||
this._stringCache.set(s, s);
|
||||
result = s;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
class JsonStringInternalizer {
|
||||
constructor(pool) {
|
||||
this._pool = pool;
|
||||
}
|
||||
traverse(value) {
|
||||
if (typeof value !== "object")
|
||||
return;
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (typeof value[i] === "string")
|
||||
value[i] = this.intern(value[i]);
|
||||
else
|
||||
this.traverse(value[i]);
|
||||
}
|
||||
} else {
|
||||
for (const name in value) {
|
||||
if (typeof value[name] === "string")
|
||||
value[name] = this.intern(value[name]);
|
||||
else
|
||||
this.traverse(value[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
intern(value) {
|
||||
return this._pool.internString(value);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
JsonStringInternalizer,
|
||||
StringInternPool
|
||||
});
|
||||
+538
@@ -0,0 +1,538 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var teleReceiver_exports = {};
|
||||
__export(teleReceiver_exports, {
|
||||
TeleReporterReceiver: () => TeleReporterReceiver,
|
||||
TeleSuite: () => TeleSuite,
|
||||
TeleTestCase: () => TeleTestCase,
|
||||
TeleTestResult: () => TeleTestResult,
|
||||
asFullConfig: () => asFullConfig,
|
||||
asFullResult: () => asFullResult,
|
||||
baseFullConfig: () => baseFullConfig,
|
||||
computeTestCaseOutcome: () => computeTestCaseOutcome,
|
||||
parseRegexPatterns: () => parseRegexPatterns,
|
||||
serializeRegexPatterns: () => serializeRegexPatterns
|
||||
});
|
||||
module.exports = __toCommonJS(teleReceiver_exports);
|
||||
class TeleReporterReceiver {
|
||||
constructor(reporter, options = {}) {
|
||||
this.isListing = false;
|
||||
this._tests = /* @__PURE__ */ new Map();
|
||||
this._rootSuite = new TeleSuite("", "root");
|
||||
this._options = options;
|
||||
this._reporter = reporter;
|
||||
}
|
||||
reset() {
|
||||
this._rootSuite._entries = [];
|
||||
this._tests.clear();
|
||||
}
|
||||
dispatch(message) {
|
||||
const { method, params } = message;
|
||||
if (method === "onConfigure") {
|
||||
this._onConfigure(params.config);
|
||||
return;
|
||||
}
|
||||
if (method === "onProject") {
|
||||
this._onProject(params.project);
|
||||
return;
|
||||
}
|
||||
if (method === "onBegin") {
|
||||
this._onBegin();
|
||||
return;
|
||||
}
|
||||
if (method === "onTestBegin") {
|
||||
this._onTestBegin(params.testId, params.result);
|
||||
return;
|
||||
}
|
||||
if (method === "onTestPaused") {
|
||||
this._onTestPaused(params.testId, params.resultId, params.errors);
|
||||
return;
|
||||
}
|
||||
if (method === "onTestEnd") {
|
||||
this._onTestEnd(params.test, params.result);
|
||||
return;
|
||||
}
|
||||
if (method === "onStepBegin") {
|
||||
this._onStepBegin(params.testId, params.resultId, params.step);
|
||||
return;
|
||||
}
|
||||
if (method === "onAttach") {
|
||||
this._onAttach(params.testId, params.resultId, params.attachments);
|
||||
return;
|
||||
}
|
||||
if (method === "onStepEnd") {
|
||||
this._onStepEnd(params.testId, params.resultId, params.step);
|
||||
return;
|
||||
}
|
||||
if (method === "onError") {
|
||||
this._onError(params.error);
|
||||
return;
|
||||
}
|
||||
if (method === "onStdIO") {
|
||||
this._onStdIO(params.type, params.testId, params.resultId, params.data, params.isBase64);
|
||||
return;
|
||||
}
|
||||
if (method === "onEnd")
|
||||
return this._onEnd(params.result);
|
||||
if (method === "onExit")
|
||||
return this._onExit();
|
||||
}
|
||||
_onConfigure(config) {
|
||||
this._rootDir = config.rootDir;
|
||||
this._config = this._parseConfig(config);
|
||||
this._reporter.onConfigure?.(this._config);
|
||||
}
|
||||
_onProject(project) {
|
||||
let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find((suite) => suite.project().name === project.name) : void 0;
|
||||
if (!projectSuite) {
|
||||
projectSuite = new TeleSuite(project.name, "project");
|
||||
this._rootSuite._addSuite(projectSuite);
|
||||
}
|
||||
const parsed = this._parseProject(project);
|
||||
projectSuite._project = parsed;
|
||||
let index = -1;
|
||||
if (this._options.mergeProjects)
|
||||
index = this._config.projects.findIndex((p) => p.name === project.name);
|
||||
if (index === -1)
|
||||
this._config.projects.push(parsed);
|
||||
else
|
||||
this._config.projects[index] = parsed;
|
||||
for (const suite of project.suites)
|
||||
this._mergeSuiteInto(suite, projectSuite);
|
||||
}
|
||||
_onBegin() {
|
||||
this._reporter.onBegin?.(this._rootSuite);
|
||||
}
|
||||
_onTestBegin(testId, payload) {
|
||||
const test = this._tests.get(testId);
|
||||
if (this._options.clearPreviousResultsWhenTestBegins)
|
||||
test.results = [];
|
||||
const testResult = test._createTestResult(payload.id);
|
||||
testResult.retry = payload.retry;
|
||||
testResult.workerIndex = payload.workerIndex;
|
||||
testResult.parallelIndex = payload.parallelIndex;
|
||||
testResult.setStartTimeNumber(payload.startTime);
|
||||
this._reporter.onTestBegin?.(test, testResult);
|
||||
}
|
||||
_onTestPaused(testId, resultId, errors) {
|
||||
const test = this._tests.get(testId);
|
||||
const result = test.results.find((r) => r._id === resultId);
|
||||
result.errors.push(...errors);
|
||||
result.error = result.errors[0];
|
||||
void this._reporter.onTestPaused?.(test, result);
|
||||
}
|
||||
_onTestEnd(testEndPayload, payload) {
|
||||
const test = this._tests.get(testEndPayload.testId);
|
||||
test.timeout = testEndPayload.timeout;
|
||||
test.expectedStatus = testEndPayload.expectedStatus;
|
||||
const result = test.results.find((r) => r._id === payload.id);
|
||||
result.duration = payload.duration;
|
||||
result.status = payload.status;
|
||||
result.errors.push(...payload.errors ?? []);
|
||||
result.error = result.errors[0];
|
||||
if (!!payload.attachments)
|
||||
result.attachments = this._parseAttachments(payload.attachments);
|
||||
if (payload.annotations) {
|
||||
this._absoluteAnnotationLocationsInplace(payload.annotations);
|
||||
result.annotations = payload.annotations;
|
||||
test.annotations = payload.annotations;
|
||||
}
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
result._stepMap = /* @__PURE__ */ new Map();
|
||||
}
|
||||
_onStepBegin(testId, resultId, payload) {
|
||||
const test = this._tests.get(testId);
|
||||
const result = test.results.find((r) => r._id === resultId);
|
||||
const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : void 0;
|
||||
const location = this._absoluteLocation(payload.location);
|
||||
const step = new TeleTestStep(payload, parentStep, location, result);
|
||||
if (parentStep)
|
||||
parentStep.steps.push(step);
|
||||
else
|
||||
result.steps.push(step);
|
||||
result._stepMap.set(payload.id, step);
|
||||
this._reporter.onStepBegin?.(test, result, step);
|
||||
}
|
||||
_onStepEnd(testId, resultId, payload) {
|
||||
const test = this._tests.get(testId);
|
||||
const result = test.results.find((r) => r._id === resultId);
|
||||
const step = result._stepMap.get(payload.id);
|
||||
step._endPayload = payload;
|
||||
step.duration = payload.duration;
|
||||
step.error = payload.error;
|
||||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
_onAttach(testId, resultId, attachments) {
|
||||
const test = this._tests.get(testId);
|
||||
const result = test.results.find((r) => r._id === resultId);
|
||||
result.attachments.push(...attachments.map((a) => ({
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path,
|
||||
body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0
|
||||
})));
|
||||
}
|
||||
_onError(error) {
|
||||
this._reporter.onError?.(error);
|
||||
}
|
||||
_onStdIO(type, testId, resultId, data, isBase64) {
|
||||
const chunk = isBase64 ? globalThis.Buffer ? Buffer.from(data, "base64") : atob(data) : data;
|
||||
const test = testId ? this._tests.get(testId) : void 0;
|
||||
const result = test && resultId ? test.results.find((r) => r._id === resultId) : void 0;
|
||||
if (type === "stdout") {
|
||||
result?.stdout.push(chunk);
|
||||
this._reporter.onStdOut?.(chunk, test, result);
|
||||
} else {
|
||||
result?.stderr.push(chunk);
|
||||
this._reporter.onStdErr?.(chunk, test, result);
|
||||
}
|
||||
}
|
||||
async _onEnd(result) {
|
||||
await this._reporter.onEnd?.(asFullResult(result));
|
||||
}
|
||||
_onExit() {
|
||||
return this._reporter.onExit?.();
|
||||
}
|
||||
_parseConfig(config) {
|
||||
const result = asFullConfig(config);
|
||||
if (this._options.configOverrides) {
|
||||
result.configFile = this._options.configOverrides.configFile;
|
||||
result.reportSlowTests = this._options.configOverrides.reportSlowTests;
|
||||
result.quiet = this._options.configOverrides.quiet;
|
||||
result.reporter = [...this._options.configOverrides.reporter];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
_parseProject(project) {
|
||||
return {
|
||||
metadata: project.metadata,
|
||||
name: project.name,
|
||||
outputDir: this._absolutePath(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
testDir: this._absolutePath(project.testDir),
|
||||
testIgnore: parseRegexPatterns(project.testIgnore),
|
||||
testMatch: parseRegexPatterns(project.testMatch),
|
||||
timeout: project.timeout,
|
||||
grep: parseRegexPatterns(project.grep),
|
||||
grepInvert: parseRegexPatterns(project.grepInvert),
|
||||
dependencies: project.dependencies,
|
||||
teardown: project.teardown,
|
||||
snapshotDir: this._absolutePath(project.snapshotDir),
|
||||
ignoreSnapshots: project.ignoreSnapshots ?? false,
|
||||
use: project.use
|
||||
};
|
||||
}
|
||||
_parseAttachments(attachments) {
|
||||
return attachments.map((a) => {
|
||||
return {
|
||||
...a,
|
||||
body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0
|
||||
};
|
||||
});
|
||||
}
|
||||
_mergeSuiteInto(jsonSuite, parent) {
|
||||
let targetSuite = parent.suites.find((s) => s.title === jsonSuite.title);
|
||||
if (!targetSuite) {
|
||||
targetSuite = new TeleSuite(jsonSuite.title, parent.type === "project" ? "file" : "describe");
|
||||
parent._addSuite(targetSuite);
|
||||
}
|
||||
targetSuite.location = this._absoluteLocation(jsonSuite.location);
|
||||
jsonSuite.entries.forEach((e) => {
|
||||
if ("testId" in e)
|
||||
this._mergeTestInto(e, targetSuite);
|
||||
else
|
||||
this._mergeSuiteInto(e, targetSuite);
|
||||
});
|
||||
}
|
||||
_mergeTestInto(jsonTest, parent) {
|
||||
let targetTest = this._options.mergeTestCases ? parent.tests.find((s) => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : void 0;
|
||||
if (!targetTest) {
|
||||
targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex);
|
||||
parent._addTest(targetTest);
|
||||
this._tests.set(targetTest.id, targetTest);
|
||||
}
|
||||
this._updateTest(jsonTest, targetTest);
|
||||
}
|
||||
_updateTest(payload, test) {
|
||||
test.id = payload.testId;
|
||||
test.location = this._absoluteLocation(payload.location);
|
||||
test.retries = payload.retries;
|
||||
test.tags = payload.tags ?? [];
|
||||
test.annotations = payload.annotations ?? [];
|
||||
this._absoluteAnnotationLocationsInplace(test.annotations);
|
||||
return test;
|
||||
}
|
||||
_absoluteAnnotationLocationsInplace(annotations) {
|
||||
for (const annotation of annotations) {
|
||||
if (annotation.location)
|
||||
annotation.location = this._absoluteLocation(annotation.location);
|
||||
}
|
||||
}
|
||||
_absoluteLocation(location) {
|
||||
if (!location)
|
||||
return location;
|
||||
return {
|
||||
...location,
|
||||
file: this._absolutePath(location.file)
|
||||
};
|
||||
}
|
||||
_absolutePath(relativePath) {
|
||||
if (relativePath === void 0)
|
||||
return;
|
||||
return this._options.resolvePath ? this._options.resolvePath(this._rootDir, relativePath) : this._rootDir + "/" + relativePath;
|
||||
}
|
||||
}
|
||||
class TeleSuite {
|
||||
constructor(title, type) {
|
||||
this._entries = [];
|
||||
this._requireFile = "";
|
||||
this._parallelMode = "none";
|
||||
this.title = title;
|
||||
this._type = type;
|
||||
}
|
||||
get type() {
|
||||
return this._type;
|
||||
}
|
||||
get suites() {
|
||||
return this._entries.filter((e) => e.type !== "test");
|
||||
}
|
||||
get tests() {
|
||||
return this._entries.filter((e) => e.type === "test");
|
||||
}
|
||||
entries() {
|
||||
return this._entries;
|
||||
}
|
||||
allTests() {
|
||||
const result = [];
|
||||
const visit = (suite) => {
|
||||
for (const entry of suite.entries()) {
|
||||
if (entry.type === "test")
|
||||
result.push(entry);
|
||||
else
|
||||
visit(entry);
|
||||
}
|
||||
};
|
||||
visit(this);
|
||||
return result;
|
||||
}
|
||||
titlePath() {
|
||||
const titlePath = this.parent ? this.parent.titlePath() : [];
|
||||
if (this.title || this._type !== "describe")
|
||||
titlePath.push(this.title);
|
||||
return titlePath;
|
||||
}
|
||||
project() {
|
||||
return this._project ?? this.parent?.project();
|
||||
}
|
||||
_addTest(test) {
|
||||
test.parent = this;
|
||||
this._entries.push(test);
|
||||
}
|
||||
_addSuite(suite) {
|
||||
suite.parent = this;
|
||||
this._entries.push(suite);
|
||||
}
|
||||
}
|
||||
class TeleTestCase {
|
||||
constructor(id, title, location, repeatEachIndex) {
|
||||
this.fn = () => {
|
||||
};
|
||||
this.results = [];
|
||||
this.type = "test";
|
||||
this.expectedStatus = "passed";
|
||||
this.timeout = 0;
|
||||
this.annotations = [];
|
||||
this.retries = 0;
|
||||
this.tags = [];
|
||||
this.repeatEachIndex = 0;
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
this.location = location;
|
||||
this.repeatEachIndex = repeatEachIndex;
|
||||
}
|
||||
titlePath() {
|
||||
const titlePath = this.parent ? this.parent.titlePath() : [];
|
||||
titlePath.push(this.title);
|
||||
return titlePath;
|
||||
}
|
||||
outcome() {
|
||||
return computeTestCaseOutcome(this);
|
||||
}
|
||||
ok() {
|
||||
const status = this.outcome();
|
||||
return status === "expected" || status === "flaky" || status === "skipped";
|
||||
}
|
||||
_createTestResult(id) {
|
||||
const result = new TeleTestResult(this.results.length, id);
|
||||
this.results.push(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
class TeleTestStep {
|
||||
constructor(payload, parentStep, location, result) {
|
||||
this.duration = -1;
|
||||
this.steps = [];
|
||||
this._startTime = 0;
|
||||
this.title = payload.title;
|
||||
this.category = payload.category;
|
||||
this.location = location;
|
||||
this.parent = parentStep;
|
||||
this._startTime = payload.startTime;
|
||||
this._result = result;
|
||||
}
|
||||
titlePath() {
|
||||
const parentPath = this.parent?.titlePath() || [];
|
||||
return [...parentPath, this.title];
|
||||
}
|
||||
get startTime() {
|
||||
return new Date(this._startTime);
|
||||
}
|
||||
set startTime(value) {
|
||||
this._startTime = +value;
|
||||
}
|
||||
get attachments() {
|
||||
return this._endPayload?.attachments?.map((index) => this._result.attachments[index]) ?? [];
|
||||
}
|
||||
get annotations() {
|
||||
return this._endPayload?.annotations ?? [];
|
||||
}
|
||||
}
|
||||
class TeleTestResult {
|
||||
constructor(retry, id) {
|
||||
this.parallelIndex = -1;
|
||||
this.workerIndex = -1;
|
||||
this.duration = -1;
|
||||
this.stdout = [];
|
||||
this.stderr = [];
|
||||
this.attachments = [];
|
||||
this.annotations = [];
|
||||
this.status = "skipped";
|
||||
this.steps = [];
|
||||
this.errors = [];
|
||||
this._stepMap = /* @__PURE__ */ new Map();
|
||||
this._startTime = 0;
|
||||
this.retry = retry;
|
||||
this._id = id;
|
||||
}
|
||||
setStartTimeNumber(startTime) {
|
||||
this._startTime = startTime;
|
||||
}
|
||||
get startTime() {
|
||||
return new Date(this._startTime);
|
||||
}
|
||||
set startTime(value) {
|
||||
this._startTime = +value;
|
||||
}
|
||||
}
|
||||
const baseFullConfig = {
|
||||
forbidOnly: false,
|
||||
fullyParallel: false,
|
||||
globalSetup: null,
|
||||
globalTeardown: null,
|
||||
globalTimeout: 0,
|
||||
grep: /.*/,
|
||||
grepInvert: null,
|
||||
maxFailures: 0,
|
||||
metadata: {},
|
||||
preserveOutput: "always",
|
||||
projects: [],
|
||||
reporter: [[process.env.CI ? "dot" : "list"]],
|
||||
reportSlowTests: {
|
||||
max: 5,
|
||||
threshold: 3e5
|
||||
/* 5 minutes */
|
||||
},
|
||||
configFile: "",
|
||||
rootDir: "",
|
||||
quiet: false,
|
||||
shard: null,
|
||||
tags: [],
|
||||
updateSnapshots: "missing",
|
||||
updateSourceMethod: "patch",
|
||||
version: "",
|
||||
workers: 0,
|
||||
webServer: null
|
||||
};
|
||||
function serializeRegexPatterns(patterns) {
|
||||
if (!Array.isArray(patterns))
|
||||
patterns = [patterns];
|
||||
return patterns.map((s) => {
|
||||
if (typeof s === "string")
|
||||
return { s };
|
||||
return { r: { source: s.source, flags: s.flags } };
|
||||
});
|
||||
}
|
||||
function parseRegexPatterns(patterns) {
|
||||
return patterns.map((p) => {
|
||||
if (p.s !== void 0)
|
||||
return p.s;
|
||||
return new RegExp(p.r.source, p.r.flags);
|
||||
});
|
||||
}
|
||||
function computeTestCaseOutcome(test) {
|
||||
let skipped = 0;
|
||||
let didNotRun = 0;
|
||||
let expected = 0;
|
||||
let interrupted = 0;
|
||||
let unexpected = 0;
|
||||
for (const result of test.results) {
|
||||
if (result.status === "interrupted") {
|
||||
++interrupted;
|
||||
} else if (result.status === "skipped" && test.expectedStatus === "skipped") {
|
||||
++skipped;
|
||||
} else if (result.status === "skipped") {
|
||||
++didNotRun;
|
||||
} else if (result.status === test.expectedStatus) {
|
||||
++expected;
|
||||
} else {
|
||||
++unexpected;
|
||||
}
|
||||
}
|
||||
if (expected === 0 && unexpected === 0)
|
||||
return "skipped";
|
||||
if (unexpected === 0)
|
||||
return "expected";
|
||||
if (expected === 0 && skipped === 0)
|
||||
return "unexpected";
|
||||
return "flaky";
|
||||
}
|
||||
function asFullResult(result) {
|
||||
return {
|
||||
status: result.status,
|
||||
startTime: new Date(result.startTime),
|
||||
duration: result.duration
|
||||
};
|
||||
}
|
||||
function asFullConfig(config) {
|
||||
return { ...baseFullConfig, ...config };
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TeleReporterReceiver,
|
||||
TeleSuite,
|
||||
TeleTestCase,
|
||||
TeleTestResult,
|
||||
asFullConfig,
|
||||
asFullResult,
|
||||
baseFullConfig,
|
||||
computeTestCaseOutcome,
|
||||
parseRegexPatterns,
|
||||
serializeRegexPatterns
|
||||
});
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var teleSuiteUpdater_exports = {};
|
||||
__export(teleSuiteUpdater_exports, {
|
||||
TeleSuiteUpdater: () => TeleSuiteUpdater
|
||||
});
|
||||
module.exports = __toCommonJS(teleSuiteUpdater_exports);
|
||||
var import_teleReceiver = require("./teleReceiver");
|
||||
var import_testTree = require("./testTree");
|
||||
class TeleSuiteUpdater {
|
||||
constructor(options) {
|
||||
this.loadErrors = [];
|
||||
this.progress = {
|
||||
total: 0,
|
||||
passed: 0,
|
||||
failed: 0,
|
||||
skipped: 0
|
||||
};
|
||||
this._lastRunTestCount = 0;
|
||||
this._receiver = new import_teleReceiver.TeleReporterReceiver(this._createReporter(), {
|
||||
mergeProjects: true,
|
||||
mergeTestCases: true,
|
||||
resolvePath: createPathResolve(options.pathSeparator),
|
||||
clearPreviousResultsWhenTestBegins: true
|
||||
});
|
||||
this._options = options;
|
||||
}
|
||||
_createReporter() {
|
||||
return {
|
||||
version: () => "v2",
|
||||
onConfigure: (config) => {
|
||||
this.config = config;
|
||||
this._lastRunReceiver = new import_teleReceiver.TeleReporterReceiver({
|
||||
version: () => "v2",
|
||||
onBegin: (suite) => {
|
||||
this._lastRunTestCount = suite.allTests().length;
|
||||
this._lastRunReceiver = void 0;
|
||||
}
|
||||
}, {
|
||||
mergeProjects: true,
|
||||
mergeTestCases: false,
|
||||
resolvePath: createPathResolve(this._options.pathSeparator)
|
||||
});
|
||||
void this._lastRunReceiver.dispatch({ method: "onConfigure", params: { config } });
|
||||
},
|
||||
onBegin: (suite) => {
|
||||
if (!this.rootSuite)
|
||||
this.rootSuite = suite;
|
||||
if (this._testResultsSnapshot) {
|
||||
for (const test of this.rootSuite.allTests())
|
||||
test.results = this._testResultsSnapshot?.get(test.id) || test.results;
|
||||
this._testResultsSnapshot = void 0;
|
||||
}
|
||||
this.progress.total = this._lastRunTestCount;
|
||||
this.progress.passed = 0;
|
||||
this.progress.failed = 0;
|
||||
this.progress.skipped = 0;
|
||||
this._options.onUpdate(true);
|
||||
},
|
||||
onEnd: () => {
|
||||
this._options.onUpdate(true);
|
||||
},
|
||||
onTestBegin: (test, testResult) => {
|
||||
testResult[import_testTree.statusEx] = "running";
|
||||
this._options.onUpdate();
|
||||
},
|
||||
onTestEnd: (test, testResult) => {
|
||||
if (test.outcome() === "skipped")
|
||||
++this.progress.skipped;
|
||||
else if (test.outcome() === "unexpected")
|
||||
++this.progress.failed;
|
||||
else
|
||||
++this.progress.passed;
|
||||
testResult[import_testTree.statusEx] = testResult.status;
|
||||
this._options.onUpdate();
|
||||
},
|
||||
onError: (error) => this._handleOnError(error),
|
||||
printsToStdio: () => false
|
||||
};
|
||||
}
|
||||
processGlobalReport(report) {
|
||||
const receiver = new import_teleReceiver.TeleReporterReceiver({
|
||||
version: () => "v2",
|
||||
onConfigure: (c) => {
|
||||
this.config = c;
|
||||
},
|
||||
onError: (error) => this._handleOnError(error)
|
||||
});
|
||||
for (const message of report)
|
||||
void receiver.dispatch(message);
|
||||
}
|
||||
processListReport(report) {
|
||||
const tests = this.rootSuite?.allTests() || [];
|
||||
this._testResultsSnapshot = new Map(tests.map((test) => [test.id, test.results]));
|
||||
this._receiver.reset();
|
||||
for (const message of report)
|
||||
void this._receiver.dispatch(message);
|
||||
}
|
||||
processTestReportEvent(message) {
|
||||
this._lastRunReceiver?.dispatch(message)?.catch(() => {
|
||||
});
|
||||
this._receiver.dispatch(message)?.catch(() => {
|
||||
});
|
||||
}
|
||||
_handleOnError(error) {
|
||||
this.loadErrors.push(error);
|
||||
this._options.onError?.(error);
|
||||
this._options.onUpdate();
|
||||
}
|
||||
asModel() {
|
||||
return {
|
||||
rootSuite: this.rootSuite || new import_teleReceiver.TeleSuite("", "root"),
|
||||
config: this.config,
|
||||
loadErrors: this.loadErrors,
|
||||
progress: this.progress
|
||||
};
|
||||
}
|
||||
}
|
||||
function createPathResolve(pathSeparator) {
|
||||
return (rootDir, relativePath) => {
|
||||
const segments = [];
|
||||
for (const segment of [...rootDir.split(pathSeparator), ...relativePath.split(pathSeparator)]) {
|
||||
const isAfterDrive = pathSeparator === "\\" && segments.length === 1 && segments[0].endsWith(":");
|
||||
const isFirst = !segments.length;
|
||||
if (!segment && !isFirst && !isAfterDrive)
|
||||
continue;
|
||||
if (segment === ".")
|
||||
continue;
|
||||
if (segment === "..") {
|
||||
segments.pop();
|
||||
continue;
|
||||
}
|
||||
segments.push(segment);
|
||||
}
|
||||
return segments.join(pathSeparator);
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TeleSuiteUpdater
|
||||
});
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testServerConnection_exports = {};
|
||||
__export(testServerConnection_exports, {
|
||||
TestServerConnection: () => TestServerConnection,
|
||||
TestServerConnectionClosedError: () => TestServerConnectionClosedError,
|
||||
WebSocketTestServerTransport: () => WebSocketTestServerTransport
|
||||
});
|
||||
module.exports = __toCommonJS(testServerConnection_exports);
|
||||
var events = __toESM(require("./events"));
|
||||
class TestServerConnectionClosedError extends Error {
|
||||
}
|
||||
class WebSocketTestServerTransport {
|
||||
constructor(url) {
|
||||
this._ws = new WebSocket(url);
|
||||
}
|
||||
onmessage(listener) {
|
||||
this._ws.addEventListener("message", (event) => listener(event.data.toString()));
|
||||
}
|
||||
onopen(listener) {
|
||||
this._ws.addEventListener("open", listener);
|
||||
}
|
||||
onerror(listener) {
|
||||
this._ws.addEventListener("error", listener);
|
||||
}
|
||||
onclose(listener) {
|
||||
this._ws.addEventListener("close", listener);
|
||||
}
|
||||
send(data) {
|
||||
this._ws.send(data);
|
||||
}
|
||||
close() {
|
||||
this._ws.close();
|
||||
}
|
||||
}
|
||||
class TestServerConnection {
|
||||
constructor(transport) {
|
||||
this._onCloseEmitter = new events.EventEmitter();
|
||||
this._onReportEmitter = new events.EventEmitter();
|
||||
this._onStdioEmitter = new events.EventEmitter();
|
||||
this._onTestFilesChangedEmitter = new events.EventEmitter();
|
||||
this._onLoadTraceRequestedEmitter = new events.EventEmitter();
|
||||
this._onTestPausedEmitter = new events.EventEmitter();
|
||||
this._lastId = 0;
|
||||
this._callbacks = /* @__PURE__ */ new Map();
|
||||
this._isClosed = false;
|
||||
this.onClose = this._onCloseEmitter.event;
|
||||
this.onReport = this._onReportEmitter.event;
|
||||
this.onStdio = this._onStdioEmitter.event;
|
||||
this.onTestFilesChanged = this._onTestFilesChangedEmitter.event;
|
||||
this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event;
|
||||
this.onTestPaused = this._onTestPausedEmitter.event;
|
||||
this._transport = transport;
|
||||
this._transport.onmessage((data) => {
|
||||
const message = JSON.parse(data);
|
||||
const { id, result, error, method, params } = message;
|
||||
if (id) {
|
||||
const callback = this._callbacks.get(id);
|
||||
if (!callback)
|
||||
return;
|
||||
this._callbacks.delete(id);
|
||||
if (error)
|
||||
callback.reject(new Error(error));
|
||||
else
|
||||
callback.resolve(result);
|
||||
} else {
|
||||
this._dispatchEvent(method, params);
|
||||
}
|
||||
});
|
||||
const pingInterval = setInterval(() => this._sendMessage("ping").catch(() => {
|
||||
}), 3e4);
|
||||
this._connectedPromise = new Promise((f, r) => {
|
||||
this._transport.onopen(f);
|
||||
this._transport.onerror(r);
|
||||
});
|
||||
this._transport.onclose(() => {
|
||||
this._isClosed = true;
|
||||
this._onCloseEmitter.fire();
|
||||
clearInterval(pingInterval);
|
||||
for (const callback of this._callbacks.values())
|
||||
callback.reject(callback.error);
|
||||
this._callbacks.clear();
|
||||
});
|
||||
}
|
||||
isClosed() {
|
||||
return this._isClosed;
|
||||
}
|
||||
async _sendMessage(method, params) {
|
||||
const logForTest = globalThis.__logForTest;
|
||||
logForTest?.({ method, params });
|
||||
await this._connectedPromise;
|
||||
const id = ++this._lastId;
|
||||
const message = { id, method, params };
|
||||
const error = new TestServerConnectionClosedError(`${method}: test server connection closed`);
|
||||
this._transport.send(JSON.stringify(message));
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, { resolve, reject, error });
|
||||
});
|
||||
}
|
||||
_sendMessageNoReply(method, params) {
|
||||
this._sendMessage(method, params).catch(() => {
|
||||
});
|
||||
}
|
||||
_dispatchEvent(method, params) {
|
||||
if (method === "report")
|
||||
this._onReportEmitter.fire(params);
|
||||
else if (method === "stdio")
|
||||
this._onStdioEmitter.fire(params);
|
||||
else if (method === "testFilesChanged")
|
||||
this._onTestFilesChangedEmitter.fire(params);
|
||||
else if (method === "loadTraceRequested")
|
||||
this._onLoadTraceRequestedEmitter.fire(params);
|
||||
else if (method === "testPaused")
|
||||
this._onTestPausedEmitter.fire(params);
|
||||
}
|
||||
async initialize(params) {
|
||||
await this._sendMessage("initialize", params);
|
||||
}
|
||||
async ping(params) {
|
||||
await this._sendMessage("ping", params);
|
||||
}
|
||||
async pingNoReply(params) {
|
||||
this._sendMessageNoReply("ping", params);
|
||||
}
|
||||
async watch(params) {
|
||||
await this._sendMessage("watch", params);
|
||||
}
|
||||
watchNoReply(params) {
|
||||
this._sendMessageNoReply("watch", params);
|
||||
}
|
||||
async open(params) {
|
||||
await this._sendMessage("open", params);
|
||||
}
|
||||
openNoReply(params) {
|
||||
this._sendMessageNoReply("open", params);
|
||||
}
|
||||
async resizeTerminal(params) {
|
||||
await this._sendMessage("resizeTerminal", params);
|
||||
}
|
||||
resizeTerminalNoReply(params) {
|
||||
this._sendMessageNoReply("resizeTerminal", params);
|
||||
}
|
||||
async checkBrowsers(params) {
|
||||
return await this._sendMessage("checkBrowsers", params);
|
||||
}
|
||||
async installBrowsers(params) {
|
||||
await this._sendMessage("installBrowsers", params);
|
||||
}
|
||||
async runGlobalSetup(params) {
|
||||
return await this._sendMessage("runGlobalSetup", params);
|
||||
}
|
||||
async runGlobalTeardown(params) {
|
||||
return await this._sendMessage("runGlobalTeardown", params);
|
||||
}
|
||||
async startDevServer(params) {
|
||||
return await this._sendMessage("startDevServer", params);
|
||||
}
|
||||
async stopDevServer(params) {
|
||||
return await this._sendMessage("stopDevServer", params);
|
||||
}
|
||||
async clearCache(params) {
|
||||
return await this._sendMessage("clearCache", params);
|
||||
}
|
||||
async listFiles(params) {
|
||||
return await this._sendMessage("listFiles", params);
|
||||
}
|
||||
async listTests(params) {
|
||||
return await this._sendMessage("listTests", params);
|
||||
}
|
||||
async runTests(params) {
|
||||
return await this._sendMessage("runTests", params);
|
||||
}
|
||||
async findRelatedTestFiles(params) {
|
||||
return await this._sendMessage("findRelatedTestFiles", params);
|
||||
}
|
||||
async stopTests(params) {
|
||||
await this._sendMessage("stopTests", params);
|
||||
}
|
||||
stopTestsNoReply(params) {
|
||||
this._sendMessageNoReply("stopTests", params);
|
||||
}
|
||||
async closeGracefully(params) {
|
||||
await this._sendMessage("closeGracefully", params);
|
||||
}
|
||||
close() {
|
||||
try {
|
||||
this._transport.close();
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TestServerConnection,
|
||||
TestServerConnectionClosedError,
|
||||
WebSocketTestServerTransport
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testServerInterface_exports = {};
|
||||
module.exports = __toCommonJS(testServerInterface_exports);
|
||||
+329
@@ -0,0 +1,329 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testTree_exports = {};
|
||||
__export(testTree_exports, {
|
||||
TestTree: () => TestTree,
|
||||
sortAndPropagateStatus: () => sortAndPropagateStatus,
|
||||
statusEx: () => statusEx
|
||||
});
|
||||
module.exports = __toCommonJS(testTree_exports);
|
||||
class TestTree {
|
||||
constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator, hideFiles) {
|
||||
this._treeItemById = /* @__PURE__ */ new Map();
|
||||
this._treeItemByTestId = /* @__PURE__ */ new Map();
|
||||
const filterProjects = projectFilters && [...projectFilters.values()].some(Boolean);
|
||||
this.pathSeparator = pathSeparator;
|
||||
this.rootItem = {
|
||||
kind: "group",
|
||||
subKind: "folder",
|
||||
id: rootFolder,
|
||||
title: "",
|
||||
location: { file: "", line: 0, column: 0 },
|
||||
duration: 0,
|
||||
parent: void 0,
|
||||
children: [],
|
||||
status: "none",
|
||||
hasLoadErrors: false
|
||||
};
|
||||
this._treeItemById.set(rootFolder, this.rootItem);
|
||||
const visitSuite = (project, parentSuite, parentGroup, mode) => {
|
||||
for (const suite of mode === "tests" ? [] : parentSuite.suites) {
|
||||
if (!suite.title) {
|
||||
visitSuite(project, suite, parentGroup, "all");
|
||||
continue;
|
||||
}
|
||||
let group = parentGroup.children.find((item) => item.kind === "group" && item.title === suite.title);
|
||||
if (!group) {
|
||||
group = {
|
||||
kind: "group",
|
||||
subKind: "describe",
|
||||
id: "suite:" + parentSuite.titlePath().join("") + "" + suite.title,
|
||||
// account for anonymous suites
|
||||
title: suite.title,
|
||||
location: suite.location,
|
||||
duration: 0,
|
||||
parent: parentGroup,
|
||||
children: [],
|
||||
status: "none",
|
||||
hasLoadErrors: false
|
||||
};
|
||||
this._addChild(parentGroup, group);
|
||||
}
|
||||
visitSuite(project, suite, group, "all");
|
||||
}
|
||||
for (const test of mode === "suites" ? [] : parentSuite.tests) {
|
||||
const title = test.title;
|
||||
let testCaseItem = parentGroup.children.find((t) => t.kind !== "group" && t.title === title);
|
||||
if (!testCaseItem) {
|
||||
testCaseItem = {
|
||||
kind: "case",
|
||||
id: "test:" + test.titlePath().join(""),
|
||||
title,
|
||||
parent: parentGroup,
|
||||
children: [],
|
||||
tests: [],
|
||||
location: test.location,
|
||||
duration: 0,
|
||||
status: "none",
|
||||
project: void 0,
|
||||
test: void 0,
|
||||
tags: test.tags
|
||||
};
|
||||
this._addChild(parentGroup, testCaseItem);
|
||||
}
|
||||
const result = test.results[0];
|
||||
let status = "none";
|
||||
if (result?.[statusEx] === "scheduled")
|
||||
status = "scheduled";
|
||||
else if (result?.[statusEx] === "running")
|
||||
status = "running";
|
||||
else if (result?.status === "skipped")
|
||||
status = "skipped";
|
||||
else if (result?.status === "interrupted")
|
||||
status = "none";
|
||||
else if (result && test.outcome() !== "expected")
|
||||
status = "failed";
|
||||
else if (result && test.outcome() === "expected")
|
||||
status = "passed";
|
||||
testCaseItem.tests.push(test);
|
||||
const testItem = {
|
||||
kind: "test",
|
||||
id: test.id,
|
||||
title: project.name,
|
||||
location: test.location,
|
||||
test,
|
||||
parent: testCaseItem,
|
||||
children: [],
|
||||
status,
|
||||
duration: test.results.length ? Math.max(0, test.results[0].duration) : 0,
|
||||
project
|
||||
};
|
||||
this._addChild(testCaseItem, testItem);
|
||||
this._treeItemByTestId.set(test.id, testItem);
|
||||
testCaseItem.duration = testCaseItem.children.reduce((a, b) => a + b.duration, 0);
|
||||
}
|
||||
};
|
||||
for (const projectSuite of rootSuite?.suites || []) {
|
||||
if (filterProjects && !projectFilters.get(projectSuite.title))
|
||||
continue;
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
if (hideFiles) {
|
||||
visitSuite(projectSuite.project(), fileSuite, this.rootItem, "suites");
|
||||
if (fileSuite.tests.length) {
|
||||
const defaultDescribeItem = this._defaultDescribeItem();
|
||||
visitSuite(projectSuite.project(), fileSuite, defaultDescribeItem, "tests");
|
||||
}
|
||||
} else {
|
||||
const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
|
||||
visitSuite(projectSuite.project(), fileSuite, fileItem, "all");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const loadError of loadErrors) {
|
||||
if (!loadError.location)
|
||||
continue;
|
||||
const fileItem = this._fileItem(loadError.location.file.split(pathSeparator), true);
|
||||
fileItem.hasLoadErrors = true;
|
||||
}
|
||||
}
|
||||
_addChild(parent, child) {
|
||||
parent.children.push(child);
|
||||
child.parent = parent;
|
||||
this._treeItemById.set(child.id, child);
|
||||
}
|
||||
filterTree(filterText, statusFilters, runningTestIds) {
|
||||
const tokens = filterText.trim().toLowerCase().split(" ");
|
||||
const filtersStatuses = [...statusFilters.values()].some(Boolean);
|
||||
const filter = (testCase) => {
|
||||
const titleWithTags = [...testCase.tests[0].titlePath(), ...testCase.tests[0].tags].join(" ").toLowerCase();
|
||||
if (!tokens.every((token) => titleWithTags.includes(token)) && !testCase.tests.some((t) => runningTestIds?.has(t.id)))
|
||||
return false;
|
||||
testCase.children = testCase.children.filter((test) => {
|
||||
return !filtersStatuses || runningTestIds?.has(test.test.id) || statusFilters.get(test.status);
|
||||
});
|
||||
testCase.tests = testCase.children.map((c) => c.test);
|
||||
return !!testCase.children.length;
|
||||
};
|
||||
const visit = (treeItem) => {
|
||||
const newChildren = [];
|
||||
for (const child of treeItem.children) {
|
||||
if (child.kind === "case") {
|
||||
if (filter(child))
|
||||
newChildren.push(child);
|
||||
} else {
|
||||
visit(child);
|
||||
if (child.children.length || child.hasLoadErrors)
|
||||
newChildren.push(child);
|
||||
}
|
||||
}
|
||||
treeItem.children = newChildren;
|
||||
};
|
||||
visit(this.rootItem);
|
||||
}
|
||||
_fileItem(filePath, isFile) {
|
||||
if (filePath.length === 0)
|
||||
return this.rootItem;
|
||||
const fileName = filePath.join(this.pathSeparator);
|
||||
const existingFileItem = this._treeItemById.get(fileName);
|
||||
if (existingFileItem)
|
||||
return existingFileItem;
|
||||
const parentFileItem = this._fileItem(filePath.slice(0, filePath.length - 1), false);
|
||||
const fileItem = {
|
||||
kind: "group",
|
||||
subKind: isFile ? "file" : "folder",
|
||||
id: fileName,
|
||||
title: filePath[filePath.length - 1],
|
||||
location: { file: fileName, line: 0, column: 0 },
|
||||
duration: 0,
|
||||
parent: parentFileItem,
|
||||
children: [],
|
||||
status: "none",
|
||||
hasLoadErrors: false
|
||||
};
|
||||
this._addChild(parentFileItem, fileItem);
|
||||
return fileItem;
|
||||
}
|
||||
_defaultDescribeItem() {
|
||||
let defaultDescribeItem = this._treeItemById.get("<anonymous>");
|
||||
if (!defaultDescribeItem) {
|
||||
defaultDescribeItem = {
|
||||
kind: "group",
|
||||
subKind: "describe",
|
||||
id: "<anonymous>",
|
||||
title: "<anonymous>",
|
||||
location: { file: "", line: 0, column: 0 },
|
||||
duration: 0,
|
||||
parent: this.rootItem,
|
||||
children: [],
|
||||
status: "none",
|
||||
hasLoadErrors: false
|
||||
};
|
||||
this._addChild(this.rootItem, defaultDescribeItem);
|
||||
}
|
||||
return defaultDescribeItem;
|
||||
}
|
||||
sortAndPropagateStatus() {
|
||||
sortAndPropagateStatus(this.rootItem);
|
||||
}
|
||||
flattenForSingleProject() {
|
||||
const visit = (treeItem) => {
|
||||
if (treeItem.kind === "case" && treeItem.children.length === 1) {
|
||||
treeItem.project = treeItem.children[0].project;
|
||||
treeItem.test = treeItem.children[0].test;
|
||||
treeItem.children = [];
|
||||
this._treeItemByTestId.set(treeItem.test.id, treeItem);
|
||||
} else {
|
||||
treeItem.children.forEach(visit);
|
||||
}
|
||||
};
|
||||
visit(this.rootItem);
|
||||
}
|
||||
shortenRoot() {
|
||||
let shortRoot = this.rootItem;
|
||||
while (shortRoot.children.length === 1 && shortRoot.children[0].kind === "group" && shortRoot.children[0].subKind === "folder")
|
||||
shortRoot = shortRoot.children[0];
|
||||
shortRoot.location = this.rootItem.location;
|
||||
this.rootItem = shortRoot;
|
||||
}
|
||||
fileNames() {
|
||||
const result = /* @__PURE__ */ new Set();
|
||||
const visit = (treeItem) => {
|
||||
if (treeItem.kind === "group" && treeItem.subKind === "file")
|
||||
result.add(treeItem.id);
|
||||
else
|
||||
treeItem.children.forEach(visit);
|
||||
};
|
||||
visit(this.rootItem);
|
||||
return [...result];
|
||||
}
|
||||
flatTreeItems() {
|
||||
const result = [];
|
||||
const visit = (treeItem) => {
|
||||
result.push(treeItem);
|
||||
treeItem.children.forEach(visit);
|
||||
};
|
||||
visit(this.rootItem);
|
||||
return result;
|
||||
}
|
||||
treeItemById(id) {
|
||||
return this._treeItemById.get(id);
|
||||
}
|
||||
collectTestIds(treeItem) {
|
||||
return collectTestIds(treeItem);
|
||||
}
|
||||
}
|
||||
function sortAndPropagateStatus(treeItem) {
|
||||
for (const child of treeItem.children)
|
||||
sortAndPropagateStatus(child);
|
||||
if (treeItem.kind === "group") {
|
||||
treeItem.children.sort((a, b) => {
|
||||
const fc = a.location.file.localeCompare(b.location.file);
|
||||
return fc || a.location.line - b.location.line;
|
||||
});
|
||||
}
|
||||
let allPassed = treeItem.children.length > 0;
|
||||
let allSkipped = treeItem.children.length > 0;
|
||||
let hasFailed = false;
|
||||
let hasRunning = false;
|
||||
let hasScheduled = false;
|
||||
for (const child of treeItem.children) {
|
||||
allSkipped = allSkipped && child.status === "skipped";
|
||||
allPassed = allPassed && (child.status === "passed" || child.status === "skipped");
|
||||
hasFailed = hasFailed || child.status === "failed";
|
||||
hasRunning = hasRunning || child.status === "running";
|
||||
hasScheduled = hasScheduled || child.status === "scheduled";
|
||||
}
|
||||
if (hasRunning)
|
||||
treeItem.status = "running";
|
||||
else if (hasScheduled)
|
||||
treeItem.status = "scheduled";
|
||||
else if (hasFailed)
|
||||
treeItem.status = "failed";
|
||||
else if (allSkipped)
|
||||
treeItem.status = "skipped";
|
||||
else if (allPassed)
|
||||
treeItem.status = "passed";
|
||||
}
|
||||
function collectTestIds(treeItem) {
|
||||
const testIds = /* @__PURE__ */ new Set();
|
||||
const locations = /* @__PURE__ */ new Set();
|
||||
const visit = (treeItem2) => {
|
||||
if (treeItem2.kind !== "test" && treeItem2.kind !== "case") {
|
||||
treeItem2.children.forEach(visit);
|
||||
return;
|
||||
}
|
||||
let fileItem = treeItem2;
|
||||
while (fileItem && fileItem.parent && !(fileItem.kind === "group" && fileItem.subKind === "file"))
|
||||
fileItem = fileItem.parent;
|
||||
locations.add(fileItem.location.file);
|
||||
if (treeItem2.kind === "case")
|
||||
treeItem2.tests.forEach((test) => testIds.add(test.id));
|
||||
else
|
||||
testIds.add(treeItem2.id);
|
||||
};
|
||||
visit(treeItem);
|
||||
return { testIds, locations };
|
||||
}
|
||||
const statusEx = Symbol("statusEx");
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TestTree,
|
||||
sortAndPropagateStatus,
|
||||
statusEx
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var types_d_exports = {};
|
||||
module.exports = __toCommonJS(types_d_exports);
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var loaderMain_exports = {};
|
||||
__export(loaderMain_exports, {
|
||||
LoaderMain: () => LoaderMain,
|
||||
create: () => create
|
||||
});
|
||||
module.exports = __toCommonJS(loaderMain_exports);
|
||||
var import_configLoader = require("../common/configLoader");
|
||||
var import_esmLoaderHost = require("../common/esmLoaderHost");
|
||||
var import_poolBuilder = require("../common/poolBuilder");
|
||||
var import_process = require("../common/process");
|
||||
var import_testLoader = require("../common/testLoader");
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
class LoaderMain extends import_process.ProcessRunner {
|
||||
constructor(serializedConfig) {
|
||||
super();
|
||||
this._poolBuilder = import_poolBuilder.PoolBuilder.createForLoader();
|
||||
this._serializedConfig = serializedConfig;
|
||||
}
|
||||
_config() {
|
||||
if (!this._configPromise)
|
||||
this._configPromise = (0, import_configLoader.deserializeConfig)(this._serializedConfig);
|
||||
return this._configPromise;
|
||||
}
|
||||
async loadTestFile(params) {
|
||||
const testErrors = [];
|
||||
const config = await this._config();
|
||||
const fileSuite = await (0, import_testLoader.loadTestFile)(params.file, config, testErrors);
|
||||
this._poolBuilder.buildPools(fileSuite);
|
||||
return { fileSuite: fileSuite._deepSerialize(), testErrors };
|
||||
}
|
||||
async getCompilationCacheFromLoader() {
|
||||
await (0, import_esmLoaderHost.incorporateCompilationCache)();
|
||||
return (0, import_compilationCache.serializeCompilationCache)();
|
||||
}
|
||||
}
|
||||
const create = (config) => new LoaderMain(config);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
LoaderMain,
|
||||
create
|
||||
});
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var expect_exports = {};
|
||||
__export(expect_exports, {
|
||||
expect: () => expect,
|
||||
mergeExpects: () => mergeExpects
|
||||
});
|
||||
module.exports = __toCommonJS(expect_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_matcherHint = require("./matcherHint");
|
||||
var import_matchers = require("./matchers");
|
||||
var import_toMatchAriaSnapshot = require("./toMatchAriaSnapshot");
|
||||
var import_toMatchSnapshot = require("./toMatchSnapshot");
|
||||
var import_expectBundle = require("../common/expectBundle");
|
||||
var import_globals = require("../common/globals");
|
||||
var import_util = require("../util");
|
||||
var import_testInfo = require("../worker/testInfo");
|
||||
function createMatchers(actual, info, prefix) {
|
||||
return new Proxy((0, import_expectBundle.expect)(actual), new ExpectMetaInfoProxyHandler(actual, info, prefix));
|
||||
}
|
||||
const userMatchersSymbol = Symbol("userMatchers");
|
||||
function qualifiedMatcherName(qualifier, matcherName) {
|
||||
return qualifier.join(":") + "$" + matcherName;
|
||||
}
|
||||
function createExpect(info, prefix, userMatchers) {
|
||||
const expectInstance = new Proxy(import_expectBundle.expect, {
|
||||
apply: function(target, thisArg, argumentsList) {
|
||||
const [actual, messageOrOptions] = argumentsList;
|
||||
const message = (0, import_utils.isString)(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message;
|
||||
const newInfo = { ...info, message };
|
||||
if (newInfo.poll) {
|
||||
if (typeof actual !== "function")
|
||||
throw new Error("`expect.poll()` accepts only function as a first argument");
|
||||
newInfo.poll.generator = actual;
|
||||
}
|
||||
return createMatchers(actual, newInfo, prefix);
|
||||
},
|
||||
get: function(target, property) {
|
||||
if (property === "configure")
|
||||
return configure;
|
||||
if (property === "extend") {
|
||||
return (matchers) => {
|
||||
const qualifier = [...prefix, (0, import_utils.createGuid)()];
|
||||
const wrappedMatchers = {};
|
||||
for (const [name, matcher] of Object.entries(matchers)) {
|
||||
wrappedMatchers[name] = wrapPlaywrightMatcherToPassNiceThis(matcher);
|
||||
const key = qualifiedMatcherName(qualifier, name);
|
||||
wrappedMatchers[key] = wrappedMatchers[name];
|
||||
Object.defineProperty(wrappedMatchers[key], "name", { value: name });
|
||||
}
|
||||
import_expectBundle.expect.extend(wrappedMatchers);
|
||||
return createExpect(info, qualifier, { ...userMatchers, ...matchers });
|
||||
};
|
||||
}
|
||||
if (property === "soft") {
|
||||
return (actual, messageOrOptions) => {
|
||||
return configure({ soft: true })(actual, messageOrOptions);
|
||||
};
|
||||
}
|
||||
if (property === userMatchersSymbol)
|
||||
return userMatchers;
|
||||
if (property === "poll") {
|
||||
return (actual, messageOrOptions) => {
|
||||
const poll = (0, import_utils.isString)(messageOrOptions) ? {} : messageOrOptions || {};
|
||||
return configure({ _poll: poll })(actual, messageOrOptions);
|
||||
};
|
||||
}
|
||||
return import_expectBundle.expect[property];
|
||||
}
|
||||
});
|
||||
const configure = (configuration) => {
|
||||
const newInfo = { ...info };
|
||||
if ("message" in configuration)
|
||||
newInfo.message = configuration.message;
|
||||
if ("timeout" in configuration)
|
||||
newInfo.timeout = configuration.timeout;
|
||||
if ("soft" in configuration)
|
||||
newInfo.isSoft = configuration.soft;
|
||||
if ("_poll" in configuration) {
|
||||
newInfo.poll = configuration._poll ? { ...info.poll, generator: () => {
|
||||
} } : void 0;
|
||||
if (typeof configuration._poll === "object") {
|
||||
newInfo.poll.timeout = configuration._poll.timeout ?? newInfo.poll.timeout;
|
||||
newInfo.poll.intervals = configuration._poll.intervals ?? newInfo.poll.intervals;
|
||||
}
|
||||
}
|
||||
return createExpect(newInfo, prefix, userMatchers);
|
||||
};
|
||||
return expectInstance;
|
||||
}
|
||||
let matcherCallContext;
|
||||
function setMatcherCallContext(context) {
|
||||
matcherCallContext = context;
|
||||
}
|
||||
function takeMatcherCallContext() {
|
||||
try {
|
||||
return matcherCallContext;
|
||||
} finally {
|
||||
matcherCallContext = void 0;
|
||||
}
|
||||
}
|
||||
const defaultExpectTimeout = 5e3;
|
||||
function wrapPlaywrightMatcherToPassNiceThis(matcher) {
|
||||
return function(...args) {
|
||||
const { isNot, promise, utils } = this;
|
||||
const context = takeMatcherCallContext();
|
||||
const timeout = context?.expectInfo.timeout ?? context?.testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
|
||||
const newThis = {
|
||||
isNot,
|
||||
promise,
|
||||
utils,
|
||||
timeout,
|
||||
_stepInfo: context?.step
|
||||
};
|
||||
newThis.equals = throwUnsupportedExpectMatcherError;
|
||||
return matcher.call(newThis, ...args);
|
||||
};
|
||||
}
|
||||
function throwUnsupportedExpectMatcherError() {
|
||||
throw new Error("It looks like you are using custom expect matchers that are not compatible with Playwright. See https://aka.ms/playwright/expect-compatibility");
|
||||
}
|
||||
import_expectBundle.expect.setState({ expand: false });
|
||||
const customAsyncMatchers = {
|
||||
toBeAttached: import_matchers.toBeAttached,
|
||||
toBeChecked: import_matchers.toBeChecked,
|
||||
toBeDisabled: import_matchers.toBeDisabled,
|
||||
toBeEditable: import_matchers.toBeEditable,
|
||||
toBeEmpty: import_matchers.toBeEmpty,
|
||||
toBeEnabled: import_matchers.toBeEnabled,
|
||||
toBeFocused: import_matchers.toBeFocused,
|
||||
toBeHidden: import_matchers.toBeHidden,
|
||||
toBeInViewport: import_matchers.toBeInViewport,
|
||||
toBeOK: import_matchers.toBeOK,
|
||||
toBeVisible: import_matchers.toBeVisible,
|
||||
toContainText: import_matchers.toContainText,
|
||||
toContainClass: import_matchers.toContainClass,
|
||||
toHaveAccessibleDescription: import_matchers.toHaveAccessibleDescription,
|
||||
toHaveAccessibleName: import_matchers.toHaveAccessibleName,
|
||||
toHaveAccessibleErrorMessage: import_matchers.toHaveAccessibleErrorMessage,
|
||||
toHaveAttribute: import_matchers.toHaveAttribute,
|
||||
toHaveClass: import_matchers.toHaveClass,
|
||||
toHaveCount: import_matchers.toHaveCount,
|
||||
toHaveCSS: import_matchers.toHaveCSS,
|
||||
toHaveId: import_matchers.toHaveId,
|
||||
toHaveJSProperty: import_matchers.toHaveJSProperty,
|
||||
toHaveRole: import_matchers.toHaveRole,
|
||||
toHaveText: import_matchers.toHaveText,
|
||||
toHaveTitle: import_matchers.toHaveTitle,
|
||||
toHaveURL: import_matchers.toHaveURL,
|
||||
toHaveValue: import_matchers.toHaveValue,
|
||||
toHaveValues: import_matchers.toHaveValues,
|
||||
toHaveScreenshot: import_toMatchSnapshot.toHaveScreenshot,
|
||||
toMatchAriaSnapshot: import_toMatchAriaSnapshot.toMatchAriaSnapshot,
|
||||
toPass: import_matchers.toPass
|
||||
};
|
||||
const customMatchers = {
|
||||
...customAsyncMatchers,
|
||||
toMatchSnapshot: import_toMatchSnapshot.toMatchSnapshot
|
||||
};
|
||||
class ExpectMetaInfoProxyHandler {
|
||||
constructor(actual, info, prefix) {
|
||||
this._actual = actual;
|
||||
this._info = { ...info };
|
||||
this._prefix = prefix;
|
||||
}
|
||||
get(target, matcherName, receiver) {
|
||||
if (matcherName === "toThrowError")
|
||||
matcherName = "toThrow";
|
||||
let matcher = Reflect.get(target, matcherName, receiver);
|
||||
if (typeof matcherName !== "string")
|
||||
return matcher;
|
||||
let resolvedMatcherName = matcherName;
|
||||
for (let i = this._prefix.length; i > 0; i--) {
|
||||
const qualifiedName = qualifiedMatcherName(this._prefix.slice(0, i), matcherName);
|
||||
if (Reflect.has(target, qualifiedName)) {
|
||||
matcher = Reflect.get(target, qualifiedName, receiver);
|
||||
resolvedMatcherName = qualifiedName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matcher === void 0)
|
||||
throw new Error(`expect: Property '${matcherName}' not found.`);
|
||||
if (typeof matcher !== "function") {
|
||||
if (matcherName === "not")
|
||||
this._info.isNot = !this._info.isNot;
|
||||
return new Proxy(matcher, this);
|
||||
}
|
||||
if (this._info.poll) {
|
||||
if (customAsyncMatchers[matcherName] || matcherName === "resolves" || matcherName === "rejects")
|
||||
throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`);
|
||||
matcher = (...args) => pollMatcher(resolvedMatcherName, this._info, this._prefix, ...args);
|
||||
}
|
||||
return (...args) => {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
setMatcherCallContext({ expectInfo: this._info, testInfo });
|
||||
if (!testInfo)
|
||||
return matcher.call(target, ...args);
|
||||
const customMessage = this._info.message || "";
|
||||
const suffixes = (0, import_matchers.computeMatcherTitleSuffix)(matcherName, this._actual, args);
|
||||
const defaultTitle = `${this._info.poll ? "poll " : ""}${this._info.isSoft ? "soft " : ""}${this._info.isNot ? "not " : ""}${matcherName}${suffixes.short || ""}`;
|
||||
const shortTitle = customMessage || `Expect ${(0, import_utils.escapeWithQuotes)(defaultTitle, '"')}`;
|
||||
const longTitle = shortTitle + (suffixes.long || "");
|
||||
const apiName = `expect${this._info.poll ? ".poll " : ""}${this._info.isSoft ? ".soft " : ""}${this._info.isNot ? ".not" : ""}.${matcherName}${suffixes.short || ""}`;
|
||||
const stackFrames = (0, import_util.filteredStackTrace)((0, import_utils.captureRawStack)());
|
||||
const stepInfo = {
|
||||
category: "expect",
|
||||
apiName,
|
||||
title: longTitle,
|
||||
shortTitle,
|
||||
params: args[0] ? { expected: args[0] } : void 0,
|
||||
infectParentStepsWithError: this._info.isSoft
|
||||
};
|
||||
const step = testInfo._addStep(stepInfo);
|
||||
const reportStepError = (e) => {
|
||||
const jestError = (0, import_matcherHint.isJestError)(e) ? e : null;
|
||||
const expectError = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : void 0;
|
||||
if (jestError?.matcherResult.suggestedRebaseline) {
|
||||
step.complete({ suggestedRebaseline: jestError?.matcherResult.suggestedRebaseline });
|
||||
return;
|
||||
}
|
||||
const error = expectError ?? e;
|
||||
step.complete({ error });
|
||||
if (this._info.isSoft)
|
||||
testInfo._failWithError(error);
|
||||
else
|
||||
throw error;
|
||||
};
|
||||
const finalizer = () => {
|
||||
step.complete({});
|
||||
};
|
||||
try {
|
||||
setMatcherCallContext({ expectInfo: this._info, testInfo, step: step.info });
|
||||
const callback = () => matcher.call(target, ...args);
|
||||
const result = (0, import_utils.currentZone)().with("stepZone", step).run(callback);
|
||||
if (result instanceof Promise)
|
||||
return result.then(finalizer).catch(reportStepError);
|
||||
finalizer();
|
||||
return result;
|
||||
} catch (e) {
|
||||
void reportStepError(e);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
async function pollMatcher(qualifiedMatcherName2, info, prefix, ...args) {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
const poll = info.poll;
|
||||
const timeout = poll.timeout ?? info.timeout ?? testInfo?._projectInternal?.expect?.timeout ?? defaultExpectTimeout;
|
||||
const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : import_testInfo.TestInfoImpl._defaultDeadlineForMatcher(timeout);
|
||||
const result = await (0, import_utils.pollAgainstDeadline)(async () => {
|
||||
if (testInfo && (0, import_globals.currentTestInfo)() !== testInfo)
|
||||
return { continuePolling: false, result: void 0 };
|
||||
const innerInfo = {
|
||||
...info,
|
||||
isSoft: false,
|
||||
// soft is outside of poll, not inside
|
||||
poll: void 0
|
||||
};
|
||||
const value = await poll.generator();
|
||||
try {
|
||||
let matchers = createMatchers(value, innerInfo, prefix);
|
||||
if (info.isNot)
|
||||
matchers = matchers.not;
|
||||
matchers[qualifiedMatcherName2](...args);
|
||||
return { continuePolling: false, result: void 0 };
|
||||
} catch (error) {
|
||||
return { continuePolling: true, result: error };
|
||||
}
|
||||
}, deadline, poll.intervals ?? [100, 250, 500, 1e3]);
|
||||
if (result.timedOut) {
|
||||
const message = result.result ? [
|
||||
result.result.message,
|
||||
"",
|
||||
`Call Log:`,
|
||||
`- ${timeoutMessage}`
|
||||
].join("\n") : timeoutMessage;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
const expect = createExpect({}, [], {}).extend(customMatchers);
|
||||
function mergeExpects(...expects) {
|
||||
let merged = expect;
|
||||
for (const e of expects) {
|
||||
const internals = e[userMatchersSymbol];
|
||||
if (!internals)
|
||||
continue;
|
||||
merged = merged.extend(internals);
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
expect,
|
||||
mergeExpects
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var matcherHint_exports = {};
|
||||
__export(matcherHint_exports, {
|
||||
ExpectError: () => ExpectError,
|
||||
isJestError: () => isJestError
|
||||
});
|
||||
module.exports = __toCommonJS(matcherHint_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
class ExpectError extends Error {
|
||||
constructor(jestError, customMessage, stackFrames) {
|
||||
super("");
|
||||
this.name = jestError.name;
|
||||
this.message = jestError.message;
|
||||
this.matcherResult = jestError.matcherResult;
|
||||
if (customMessage)
|
||||
this.message = customMessage + "\n\n" + this.message;
|
||||
this.stack = this.name + ": " + this.message + "\n" + (0, import_utils.stringifyStackFrames)(stackFrames).join("\n");
|
||||
}
|
||||
}
|
||||
function isJestError(e) {
|
||||
return e instanceof Error && "matcherResult" in e && !!e.matcherResult;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
ExpectError,
|
||||
isJestError
|
||||
});
|
||||
+385
@@ -0,0 +1,385 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var matchers_exports = {};
|
||||
__export(matchers_exports, {
|
||||
computeMatcherTitleSuffix: () => computeMatcherTitleSuffix,
|
||||
toBeAttached: () => toBeAttached,
|
||||
toBeChecked: () => toBeChecked,
|
||||
toBeDisabled: () => toBeDisabled,
|
||||
toBeEditable: () => toBeEditable,
|
||||
toBeEmpty: () => toBeEmpty,
|
||||
toBeEnabled: () => toBeEnabled,
|
||||
toBeFocused: () => toBeFocused,
|
||||
toBeHidden: () => toBeHidden,
|
||||
toBeInViewport: () => toBeInViewport,
|
||||
toBeOK: () => toBeOK,
|
||||
toBeVisible: () => toBeVisible,
|
||||
toContainClass: () => toContainClass,
|
||||
toContainText: () => toContainText,
|
||||
toHaveAccessibleDescription: () => toHaveAccessibleDescription,
|
||||
toHaveAccessibleErrorMessage: () => toHaveAccessibleErrorMessage,
|
||||
toHaveAccessibleName: () => toHaveAccessibleName,
|
||||
toHaveAttribute: () => toHaveAttribute,
|
||||
toHaveCSS: () => toHaveCSS,
|
||||
toHaveClass: () => toHaveClass,
|
||||
toHaveCount: () => toHaveCount,
|
||||
toHaveId: () => toHaveId,
|
||||
toHaveJSProperty: () => toHaveJSProperty,
|
||||
toHaveRole: () => toHaveRole,
|
||||
toHaveText: () => toHaveText,
|
||||
toHaveTitle: () => toHaveTitle,
|
||||
toHaveURL: () => toHaveURL,
|
||||
toHaveValue: () => toHaveValue,
|
||||
toHaveValues: () => toHaveValues,
|
||||
toPass: () => toPass
|
||||
});
|
||||
module.exports = __toCommonJS(matchers_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
var import_toBeTruthy = require("./toBeTruthy");
|
||||
var import_toEqual = require("./toEqual");
|
||||
var import_toHaveURL = require("./toHaveURL");
|
||||
var import_toMatchText = require("./toMatchText");
|
||||
var import_toMatchSnapshot = require("./toMatchSnapshot");
|
||||
var import_config = require("../common/config");
|
||||
var import_globals = require("../common/globals");
|
||||
var import_testInfo = require("../worker/testInfo");
|
||||
function toBeAttached(locator, options) {
|
||||
const attached = !options || options.attached === void 0 || options.attached;
|
||||
const expected = attached ? "attached" : "detached";
|
||||
const arg = attached ? "" : "{ attached: false }";
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeAttached", locator, "Locator", expected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(attached ? "to.be.attached" : "to.be.detached", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeChecked(locator, options) {
|
||||
const checked = options?.checked;
|
||||
const indeterminate = options?.indeterminate;
|
||||
const expectedValue = {
|
||||
checked,
|
||||
indeterminate
|
||||
};
|
||||
let expected;
|
||||
let arg;
|
||||
if (options?.indeterminate) {
|
||||
expected = "indeterminate";
|
||||
arg = `{ indeterminate: true }`;
|
||||
} else {
|
||||
expected = options?.checked === false ? "unchecked" : "checked";
|
||||
arg = options?.checked === false ? `{ checked: false }` : "";
|
||||
}
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeChecked", locator, "Locator", expected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.checked", { isNot, timeout, expectedValue });
|
||||
}, options);
|
||||
}
|
||||
function toBeDisabled(locator, options) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeDisabled", locator, "Locator", "disabled", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.disabled", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeEditable(locator, options) {
|
||||
const editable = !options || options.editable === void 0 || options.editable;
|
||||
const expected = editable ? "editable" : "readOnly";
|
||||
const arg = editable ? "" : "{ editable: false }";
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeEditable", locator, "Locator", expected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(editable ? "to.be.editable" : "to.be.readonly", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeEmpty(locator, options) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeEmpty", locator, "Locator", "empty", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.empty", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeEnabled(locator, options) {
|
||||
const enabled = !options || options.enabled === void 0 || options.enabled;
|
||||
const expected = enabled ? "enabled" : "disabled";
|
||||
const arg = enabled ? "" : "{ enabled: false }";
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeEnabled", locator, "Locator", expected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(enabled ? "to.be.enabled" : "to.be.disabled", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeFocused(locator, options) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeFocused", locator, "Locator", "focused", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.focused", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeHidden(locator, options) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeHidden", locator, "Locator", "hidden", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.hidden", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeVisible(locator, options) {
|
||||
const visible = !options || options.visible === void 0 || options.visible;
|
||||
const expected = visible ? "visible" : "hidden";
|
||||
const arg = visible ? "" : "{ visible: false }";
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeVisible", locator, "Locator", expected, arg, async (isNot, timeout) => {
|
||||
return await locator._expect(visible ? "to.be.visible" : "to.be.hidden", { isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toBeInViewport(locator, options) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toBeInViewport", locator, "Locator", "in viewport", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.be.in.viewport", { isNot, expectedNumber: options?.ratio, timeout });
|
||||
}, options);
|
||||
}
|
||||
function toContainText(locator, expected, options = {}) {
|
||||
if (Array.isArray(expected)) {
|
||||
return import_toEqual.toEqual.call(this, "toContainText", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected, { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect("to.contain.text.array", { expectedText, isNot, useInnerText: options.useInnerText, timeout });
|
||||
}, expected, { ...options, contains: true });
|
||||
} else {
|
||||
return import_toMatchText.toMatchText.call(this, "toContainText", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { matchSubstring: true, normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect("to.have.text", { expectedText, isNot, useInnerText: options.useInnerText, timeout });
|
||||
}, expected, { ...options, matchSubstring: true });
|
||||
}
|
||||
}
|
||||
function toHaveAccessibleDescription(locator, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveAccessibleDescription", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
|
||||
return await locator._expect("to.have.accessible.description", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveAccessibleName(locator, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveAccessibleName", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
|
||||
return await locator._expect("to.have.accessible.name", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveAccessibleErrorMessage(locator, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveAccessibleErrorMessage", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase, normalizeWhiteSpace: true });
|
||||
return await locator._expect("to.have.accessible.error.message", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveAttribute(locator, name, expected, options) {
|
||||
if (!options) {
|
||||
if (typeof expected === "object" && !(0, import_utils.isRegExp)(expected)) {
|
||||
options = expected;
|
||||
expected = void 0;
|
||||
}
|
||||
}
|
||||
if (expected === void 0) {
|
||||
return import_toBeTruthy.toBeTruthy.call(this, "toHaveAttribute", locator, "Locator", "have attribute", "", async (isNot, timeout) => {
|
||||
return await locator._expect("to.have.attribute", { expressionArg: name, isNot, timeout });
|
||||
}, options);
|
||||
}
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveAttribute", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase });
|
||||
return await locator._expect("to.have.attribute.value", { expressionArg: name, expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveClass(locator, expected, options) {
|
||||
if (Array.isArray(expected)) {
|
||||
return import_toEqual.toEqual.call(this, "toHaveClass", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected);
|
||||
return await locator._expect("to.have.class.array", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
} else {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveClass", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.have.class", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
}
|
||||
function toContainClass(locator, expected, options) {
|
||||
if (Array.isArray(expected)) {
|
||||
if (expected.some((e) => (0, import_utils.isRegExp)(e)))
|
||||
throw new Error(`"expected" argument in toContainClass cannot contain RegExp values`);
|
||||
return import_toEqual.toEqual.call(this, "toContainClass", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected);
|
||||
return await locator._expect("to.contain.class.array", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
} else {
|
||||
if ((0, import_utils.isRegExp)(expected))
|
||||
throw new Error(`"expected" argument in toContainClass cannot be a RegExp value`);
|
||||
return import_toMatchText.toMatchText.call(this, "toContainClass", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.contain.class", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
}
|
||||
function toHaveCount(locator, expected, options) {
|
||||
return import_toEqual.toEqual.call(this, "toHaveCount", locator, "Locator", async (isNot, timeout) => {
|
||||
return await locator._expect("to.have.count", { expectedNumber: expected, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveCSS(locator, name, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveCSS", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.have.css", { expressionArg: name, expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveId(locator, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveId", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.have.id", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveJSProperty(locator, name, expected, options) {
|
||||
return import_toEqual.toEqual.call(this, "toHaveJSProperty", locator, "Locator", async (isNot, timeout) => {
|
||||
return await locator._expect("to.have.property", { expressionArg: name, expectedValue: expected, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveRole(locator, expected, options) {
|
||||
if (!(0, import_utils.isString)(expected))
|
||||
throw new Error(`"role" argument in toHaveRole must be a string`);
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveRole", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.have.role", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveText(locator, expected, options = {}) {
|
||||
if (Array.isArray(expected)) {
|
||||
return import_toEqual.toEqual.call(this, "toHaveText", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected, { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect("to.have.text.array", { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
}, expected, options);
|
||||
} else {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveText", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { normalizeWhiteSpace: true, ignoreCase: options.ignoreCase });
|
||||
return await locator._expect("to.have.text", { expectedText, isNot, useInnerText: options?.useInnerText, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
}
|
||||
function toHaveValue(locator, expected, options) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveValue", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected]);
|
||||
return await locator._expect("to.have.value", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveValues(locator, expected, options) {
|
||||
return import_toEqual.toEqual.call(this, "toHaveValues", locator, "Locator", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)(expected);
|
||||
return await locator._expect("to.have.values", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveTitle(page, expected, options = {}) {
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveTitle", page, "Page", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { normalizeWhiteSpace: true });
|
||||
return await page.mainFrame()._expect("to.have.title", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
function toHaveURL(page, expected, options) {
|
||||
if ((0, import_utils.isURLPattern)(expected))
|
||||
return import_toHaveURL.toHaveURLWithPredicate.call(this, page, (url) => expected.test(url.href), options);
|
||||
if (typeof expected === "function")
|
||||
return import_toHaveURL.toHaveURLWithPredicate.call(this, page, expected, options);
|
||||
const baseURL = page.context()._options.baseURL;
|
||||
expected = typeof expected === "string" ? (0, import_utils.constructURLBasedOnBaseURL)(baseURL, expected) : expected;
|
||||
return import_toMatchText.toMatchText.call(this, "toHaveURL", page, "Page", async (isNot, timeout) => {
|
||||
const expectedText = (0, import_utils.serializeExpectedTextValues)([expected], { ignoreCase: options?.ignoreCase });
|
||||
return await page.mainFrame()._expect("to.have.url", { expectedText, isNot, timeout });
|
||||
}, expected, options);
|
||||
}
|
||||
async function toBeOK(response) {
|
||||
const matcherName = "toBeOK";
|
||||
(0, import_util.expectTypes)(response, ["APIResponse"], matcherName);
|
||||
const contentType = response.headers()["content-type"];
|
||||
const isTextEncoding = contentType && (0, import_utils.isTextualMimeType)(contentType);
|
||||
const [log, text] = this.isNot === response.ok() ? await Promise.all([
|
||||
response._fetchLog(),
|
||||
isTextEncoding ? response.text() : null
|
||||
]) : [];
|
||||
const message = () => (0, import_utils.formatMatcherMessage)(this.utils, {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
matcherName,
|
||||
receiver: "response",
|
||||
expectation: "",
|
||||
log
|
||||
}) + (text === null ? "" : `
|
||||
Response text:
|
||||
${import_utils2.colors.dim(text?.substring(0, 1e3) || "")}`);
|
||||
const pass = response.ok();
|
||||
return { message, pass };
|
||||
}
|
||||
async function toPass(callback, options = {}) {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
const timeout = (0, import_config.takeFirst)(options.timeout, testInfo?._projectInternal.expect?.toPass?.timeout, 0);
|
||||
const intervals = (0, import_config.takeFirst)(options.intervals, testInfo?._projectInternal.expect?.toPass?.intervals, [100, 250, 500, 1e3]);
|
||||
const { deadline, timeoutMessage } = testInfo ? testInfo._deadlineForMatcher(timeout) : import_testInfo.TestInfoImpl._defaultDeadlineForMatcher(timeout);
|
||||
const result = await (0, import_utils.pollAgainstDeadline)(async () => {
|
||||
if (testInfo && (0, import_globals.currentTestInfo)() !== testInfo)
|
||||
return { continuePolling: false, result: void 0 };
|
||||
try {
|
||||
await callback();
|
||||
return { continuePolling: !!this.isNot, result: void 0 };
|
||||
} catch (e) {
|
||||
return { continuePolling: !this.isNot, result: e };
|
||||
}
|
||||
}, deadline, intervals);
|
||||
if (result.timedOut) {
|
||||
const message = result.result ? [
|
||||
result.result.message,
|
||||
"",
|
||||
`Call Log:`,
|
||||
`- ${timeoutMessage}`
|
||||
].join("\n") : timeoutMessage;
|
||||
return { message: () => message, pass: !!this.isNot };
|
||||
}
|
||||
return { pass: !this.isNot, message: () => "" };
|
||||
}
|
||||
function computeMatcherTitleSuffix(matcherName, receiver, args) {
|
||||
if (matcherName === "toHaveScreenshot") {
|
||||
const title = (0, import_toMatchSnapshot.toHaveScreenshotStepTitle)(...args);
|
||||
return { short: title ? `(${title})` : "" };
|
||||
}
|
||||
if (receiver && typeof receiver === "object" && receiver.constructor?.name === "Locator") {
|
||||
try {
|
||||
return { long: " " + (0, import_utils.asLocatorDescription)("javascript", receiver._selector) };
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
computeMatcherTitleSuffix,
|
||||
toBeAttached,
|
||||
toBeChecked,
|
||||
toBeDisabled,
|
||||
toBeEditable,
|
||||
toBeEmpty,
|
||||
toBeEnabled,
|
||||
toBeFocused,
|
||||
toBeHidden,
|
||||
toBeInViewport,
|
||||
toBeOK,
|
||||
toBeVisible,
|
||||
toContainClass,
|
||||
toContainText,
|
||||
toHaveAccessibleDescription,
|
||||
toHaveAccessibleErrorMessage,
|
||||
toHaveAccessibleName,
|
||||
toHaveAttribute,
|
||||
toHaveCSS,
|
||||
toHaveClass,
|
||||
toHaveCount,
|
||||
toHaveId,
|
||||
toHaveJSProperty,
|
||||
toHaveRole,
|
||||
toHaveText,
|
||||
toHaveTitle,
|
||||
toHaveURL,
|
||||
toHaveValue,
|
||||
toHaveValues,
|
||||
toPass
|
||||
});
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toBeTruthy_exports = {};
|
||||
__export(toBeTruthy_exports, {
|
||||
toBeTruthy: () => toBeTruthy
|
||||
});
|
||||
module.exports = __toCommonJS(toBeTruthy_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
async function toBeTruthy(matcherName, locator, receiverType, expected, arg, query, options = {}) {
|
||||
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
||||
const timeout = options.timeout ?? this.timeout;
|
||||
const { matches: pass, log, timedOut, received, errorMessage } = await query(!!this.isNot, timeout);
|
||||
if (pass === !this.isNot) {
|
||||
return {
|
||||
name: matcherName,
|
||||
message: () => "",
|
||||
pass,
|
||||
expected
|
||||
};
|
||||
}
|
||||
let printedReceived;
|
||||
let printedExpected;
|
||||
if (pass) {
|
||||
printedExpected = `Expected: not ${expected}`;
|
||||
printedReceived = errorMessage ? "" : `Received: ${expected}`;
|
||||
} else {
|
||||
printedExpected = `Expected: ${expected}`;
|
||||
printedReceived = errorMessage ? "" : `Received: ${received}`;
|
||||
}
|
||||
const message = () => {
|
||||
return (0, import_utils.formatMatcherMessage)(this.utils, {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
matcherName,
|
||||
expectation: arg,
|
||||
locator: locator.toString(),
|
||||
timeout,
|
||||
timedOut,
|
||||
printedExpected,
|
||||
printedReceived,
|
||||
errorMessage,
|
||||
log
|
||||
});
|
||||
};
|
||||
return {
|
||||
message,
|
||||
pass,
|
||||
actual: received,
|
||||
name: matcherName,
|
||||
expected,
|
||||
log,
|
||||
timeout: timedOut ? timeout : void 0
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toBeTruthy
|
||||
});
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toEqual_exports = {};
|
||||
__export(toEqual_exports, {
|
||||
toEqual: () => toEqual
|
||||
});
|
||||
module.exports = __toCommonJS(toEqual_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
const EXPECTED_LABEL = "Expected";
|
||||
const RECEIVED_LABEL = "Received";
|
||||
async function toEqual(matcherName, locator, receiverType, query, expected, options = {}) {
|
||||
(0, import_util.expectTypes)(locator, [receiverType], matcherName);
|
||||
const timeout = options.timeout ?? this.timeout;
|
||||
const { matches: pass, received, log, timedOut, errorMessage } = await query(!!this.isNot, timeout);
|
||||
if (pass === !this.isNot) {
|
||||
return {
|
||||
name: matcherName,
|
||||
message: () => "",
|
||||
pass,
|
||||
expected
|
||||
};
|
||||
}
|
||||
let printedReceived;
|
||||
let printedExpected;
|
||||
let printedDiff;
|
||||
if (pass) {
|
||||
printedExpected = `Expected: not ${this.utils.printExpected(expected)}`;
|
||||
printedReceived = errorMessage ? "" : `Received: ${this.utils.printReceived(received)}`;
|
||||
} else if (errorMessage) {
|
||||
printedExpected = `Expected: ${this.utils.printExpected(expected)}`;
|
||||
} else if (Array.isArray(expected) && Array.isArray(received)) {
|
||||
const normalizedExpected = expected.map((exp, index) => {
|
||||
const rec = received[index];
|
||||
if ((0, import_utils.isRegExp)(exp))
|
||||
return exp.test(rec) ? rec : exp;
|
||||
return exp;
|
||||
});
|
||||
printedDiff = this.utils.printDiffOrStringify(
|
||||
normalizedExpected,
|
||||
received,
|
||||
EXPECTED_LABEL,
|
||||
RECEIVED_LABEL,
|
||||
false
|
||||
);
|
||||
} else {
|
||||
printedDiff = this.utils.printDiffOrStringify(
|
||||
expected,
|
||||
received,
|
||||
EXPECTED_LABEL,
|
||||
RECEIVED_LABEL,
|
||||
false
|
||||
);
|
||||
}
|
||||
const message = () => {
|
||||
return (0, import_utils.formatMatcherMessage)(this.utils, {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
matcherName,
|
||||
expectation: "expected",
|
||||
locator: locator.toString(),
|
||||
timeout,
|
||||
timedOut,
|
||||
printedExpected,
|
||||
printedReceived,
|
||||
printedDiff,
|
||||
errorMessage,
|
||||
log
|
||||
});
|
||||
};
|
||||
return {
|
||||
actual: received,
|
||||
expected,
|
||||
message,
|
||||
name: matcherName,
|
||||
pass,
|
||||
log,
|
||||
timeout: timedOut ? timeout : void 0
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toEqual
|
||||
});
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toHaveURL_exports = {};
|
||||
__export(toHaveURL_exports, {
|
||||
toHaveURLWithPredicate: () => toHaveURLWithPredicate
|
||||
});
|
||||
module.exports = __toCommonJS(toHaveURL_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
async function toHaveURLWithPredicate(page, expected, options) {
|
||||
const matcherName = "toHaveURL";
|
||||
const timeout = options?.timeout ?? this.timeout;
|
||||
const baseURL = page.context()._options.baseURL;
|
||||
let conditionSucceeded = false;
|
||||
let lastCheckedURLString = void 0;
|
||||
try {
|
||||
await page.mainFrame().waitForURL(
|
||||
(url) => {
|
||||
lastCheckedURLString = url.toString();
|
||||
if (options?.ignoreCase) {
|
||||
return !this.isNot === (0, import_utils.urlMatches)(
|
||||
baseURL?.toLocaleLowerCase(),
|
||||
lastCheckedURLString.toLocaleLowerCase(),
|
||||
expected
|
||||
);
|
||||
}
|
||||
return !this.isNot === (0, import_utils.urlMatches)(baseURL, lastCheckedURLString, expected);
|
||||
},
|
||||
{ timeout }
|
||||
);
|
||||
conditionSucceeded = true;
|
||||
} catch (e) {
|
||||
conditionSucceeded = false;
|
||||
}
|
||||
if (conditionSucceeded)
|
||||
return { name: matcherName, pass: !this.isNot, message: () => "" };
|
||||
return {
|
||||
name: matcherName,
|
||||
pass: this.isNot,
|
||||
message: () => toHaveURLMessage(
|
||||
this,
|
||||
matcherName,
|
||||
expected,
|
||||
lastCheckedURLString,
|
||||
this.isNot,
|
||||
true,
|
||||
timeout
|
||||
),
|
||||
actual: lastCheckedURLString,
|
||||
timeout
|
||||
};
|
||||
}
|
||||
function toHaveURLMessage(state, matcherName, expected, received, pass, timedOut, timeout) {
|
||||
const receivedString = received || "";
|
||||
let printedReceived;
|
||||
let printedExpected;
|
||||
let printedDiff;
|
||||
if (typeof expected === "function") {
|
||||
printedExpected = `Expected: predicate to ${!state.isNot ? "succeed" : "fail"}`;
|
||||
printedReceived = `Received: ${state.utils.printReceived(receivedString)}`;
|
||||
} else {
|
||||
if (pass) {
|
||||
printedExpected = `Expected pattern: not ${state.utils.printExpected(expected)}`;
|
||||
const formattedReceived = (0, import_utils.printReceivedStringContainExpectedResult)(state.utils, receivedString, null);
|
||||
printedReceived = `Received string: ${formattedReceived}`;
|
||||
} else {
|
||||
const labelExpected = `Expected ${typeof expected === "string" ? "string" : "pattern"}`;
|
||||
printedDiff = state.utils.printDiffOrStringify(expected, receivedString, labelExpected, "Received string", false);
|
||||
}
|
||||
}
|
||||
return (0, import_utils.formatMatcherMessage)(state.utils, {
|
||||
isNot: state.isNot,
|
||||
promise: state.promise,
|
||||
matcherName,
|
||||
expectation: "expected",
|
||||
timeout,
|
||||
timedOut,
|
||||
printedExpected,
|
||||
printedReceived,
|
||||
printedDiff
|
||||
});
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toHaveURLWithPredicate
|
||||
});
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toMatchAriaSnapshot_exports = {};
|
||||
__export(toMatchAriaSnapshot_exports, {
|
||||
toMatchAriaSnapshot: () => toMatchAriaSnapshot
|
||||
});
|
||||
module.exports = __toCommonJS(toMatchAriaSnapshot_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
var import_globals = require("../common/globals");
|
||||
async function toMatchAriaSnapshot(locator, expectedParam, options = {}) {
|
||||
const matcherName = "toMatchAriaSnapshot";
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`toMatchAriaSnapshot() must be called during the test`);
|
||||
if (testInfo._projectInternal.project.ignoreSnapshots)
|
||||
return { pass: !this.isNot, message: () => "", name: "toMatchAriaSnapshot", expected: "" };
|
||||
const updateSnapshots = testInfo.config.updateSnapshots;
|
||||
let expected;
|
||||
let timeout;
|
||||
let expectedPath;
|
||||
if ((0, import_utils.isString)(expectedParam)) {
|
||||
expected = expectedParam;
|
||||
timeout = options.timeout ?? this.timeout;
|
||||
} else {
|
||||
const legacyPath = testInfo._resolveSnapshotPaths("aria", expectedParam?.name, "dontUpdateSnapshotIndex", ".yml").absoluteSnapshotPath;
|
||||
expectedPath = testInfo._resolveSnapshotPaths("aria", expectedParam?.name, "updateSnapshotIndex").absoluteSnapshotPath;
|
||||
if (!await (0, import_util.fileExistsAsync)(expectedPath) && await (0, import_util.fileExistsAsync)(legacyPath))
|
||||
expectedPath = legacyPath;
|
||||
expected = await import_fs.default.promises.readFile(expectedPath, "utf8").catch(() => "");
|
||||
timeout = expectedParam?.timeout ?? this.timeout;
|
||||
}
|
||||
const generateMissingBaseline = updateSnapshots === "missing" && !expected;
|
||||
if (generateMissingBaseline) {
|
||||
if (this.isNot) {
|
||||
const message2 = `Matchers using ".not" can't generate new baselines`;
|
||||
return { pass: this.isNot, message: () => message2, name: "toMatchAriaSnapshot" };
|
||||
} else {
|
||||
expected = `- none "Generating new baseline"`;
|
||||
}
|
||||
}
|
||||
expected = unshift(expected);
|
||||
const globalChildren = testInfo._projectInternal.expect?.toMatchAriaSnapshot?.children;
|
||||
if (globalChildren && !expected.match(/^- \/children:/m))
|
||||
expected = `- /children: ${globalChildren}
|
||||
` + expected;
|
||||
const { matches: pass, received, log, timedOut, errorMessage } = await locator._expect("to.match.aria", { expectedValue: expected, isNot: this.isNot, timeout });
|
||||
const typedReceived = received;
|
||||
const message = () => {
|
||||
let printedExpected;
|
||||
let printedReceived;
|
||||
let printedDiff;
|
||||
if (errorMessage) {
|
||||
printedExpected = `Expected: ${this.isNot ? "not " : ""}${this.utils.printExpected(expected)}`;
|
||||
} else if (pass) {
|
||||
const receivedString = (0, import_utils.printReceivedStringContainExpectedSubstring)(this.utils, typedReceived.raw, typedReceived.raw.indexOf(expected), expected.length);
|
||||
printedExpected = `Expected: not ${this.utils.printExpected(expected)}`;
|
||||
printedReceived = `Received: ${receivedString}`;
|
||||
} else {
|
||||
printedDiff = this.utils.printDiffOrStringify(expected, typedReceived.raw, "Expected", "Received", false);
|
||||
}
|
||||
return (0, import_utils.formatMatcherMessage)(this.utils, {
|
||||
isNot: this.isNot,
|
||||
promise: this.promise,
|
||||
matcherName,
|
||||
expectation: "expected",
|
||||
locator: locator.toString(),
|
||||
timeout,
|
||||
timedOut,
|
||||
printedExpected,
|
||||
printedReceived,
|
||||
printedDiff,
|
||||
errorMessage,
|
||||
log
|
||||
});
|
||||
};
|
||||
if (errorMessage)
|
||||
return { pass: this.isNot, message, name: "toMatchAriaSnapshot", expected };
|
||||
if (!this.isNot) {
|
||||
if (updateSnapshots === "all" || updateSnapshots === "changed" && pass === this.isNot || generateMissingBaseline) {
|
||||
if (expectedPath) {
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(expectedPath), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(expectedPath, typedReceived.regex, "utf8");
|
||||
const relativePath = import_path.default.relative(process.cwd(), expectedPath);
|
||||
if (updateSnapshots === "missing") {
|
||||
const message2 = `A snapshot doesn't exist at ${relativePath}, writing actual.`;
|
||||
testInfo._hasNonRetriableError = true;
|
||||
testInfo._failWithError(new Error(message2));
|
||||
} else {
|
||||
const message2 = `A snapshot is generated at ${relativePath}.`;
|
||||
console.log(message2);
|
||||
}
|
||||
return { pass: true, message: () => "", name: "toMatchAriaSnapshot" };
|
||||
} else {
|
||||
const suggestedRebaseline = `\`
|
||||
${(0, import_utils.escapeTemplateString)(indent(typedReceived.regex, "{indent} "))}
|
||||
{indent}\``;
|
||||
if (updateSnapshots === "missing") {
|
||||
const message2 = "A snapshot is not provided, generating new baseline.";
|
||||
testInfo._hasNonRetriableError = true;
|
||||
testInfo._failWithError(new Error(message2));
|
||||
}
|
||||
return { pass: false, message: () => "", name: "toMatchAriaSnapshot", suggestedRebaseline };
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: matcherName,
|
||||
expected,
|
||||
message,
|
||||
pass,
|
||||
actual: received,
|
||||
log,
|
||||
timeout: timedOut ? timeout : void 0
|
||||
};
|
||||
}
|
||||
function unshift(snapshot) {
|
||||
const lines = snapshot.split("\n");
|
||||
let whitespacePrefixLength = 100;
|
||||
for (const line of lines) {
|
||||
if (!line.trim())
|
||||
continue;
|
||||
const match = line.match(/^(\s*)/);
|
||||
if (match && match[1].length < whitespacePrefixLength)
|
||||
whitespacePrefixLength = match[1].length;
|
||||
}
|
||||
return lines.filter((t) => t.trim()).map((line) => line.substring(whitespacePrefixLength)).join("\n");
|
||||
}
|
||||
function indent(snapshot, indent2) {
|
||||
return snapshot.split("\n").map((line) => indent2 + line).join("\n");
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toMatchAriaSnapshot
|
||||
});
|
||||
+349
@@ -0,0 +1,349 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toMatchSnapshot_exports = {};
|
||||
__export(toMatchSnapshot_exports, {
|
||||
toHaveScreenshot: () => toHaveScreenshot,
|
||||
toHaveScreenshotStepTitle: () => toHaveScreenshotStepTitle,
|
||||
toMatchSnapshot: () => toMatchSnapshot
|
||||
});
|
||||
module.exports = __toCommonJS(toMatchSnapshot_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_util = require("../util");
|
||||
var import_globals = require("../common/globals");
|
||||
const NonConfigProperties = [
|
||||
"clip",
|
||||
"fullPage",
|
||||
"mask",
|
||||
"maskColor",
|
||||
"omitBackground",
|
||||
"timeout"
|
||||
];
|
||||
class SnapshotHelper {
|
||||
constructor(state, testInfo, matcherName, locator, anonymousSnapshotExtension, configOptions, nameOrOptions, optOptions) {
|
||||
let name;
|
||||
if (Array.isArray(nameOrOptions) || typeof nameOrOptions === "string") {
|
||||
name = nameOrOptions;
|
||||
this.options = { ...optOptions };
|
||||
} else {
|
||||
const { name: nameFromOptions, ...options } = nameOrOptions;
|
||||
this.options = options;
|
||||
name = nameFromOptions;
|
||||
}
|
||||
this.name = Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
|
||||
const resolvedPaths = testInfo._resolveSnapshotPaths(matcherName === "toHaveScreenshot" ? "screenshot" : "snapshot", name, "updateSnapshotIndex", anonymousSnapshotExtension);
|
||||
this.expectedPath = resolvedPaths.absoluteSnapshotPath;
|
||||
this.attachmentBaseName = resolvedPaths.relativeOutputPath;
|
||||
const outputBasePath = testInfo._getOutputPath(resolvedPaths.relativeOutputPath);
|
||||
this.legacyExpectedPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-expected");
|
||||
this.previousPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-previous");
|
||||
this.actualPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-actual");
|
||||
this.diffPath = (0, import_util.addSuffixToFilePath)(outputBasePath, "-diff");
|
||||
const filteredConfigOptions = { ...configOptions };
|
||||
for (const prop of NonConfigProperties)
|
||||
delete filteredConfigOptions[prop];
|
||||
this.options = {
|
||||
...filteredConfigOptions,
|
||||
...this.options
|
||||
};
|
||||
if (this.options._comparator) {
|
||||
this.options.comparator = this.options._comparator;
|
||||
delete this.options._comparator;
|
||||
}
|
||||
if (this.options.maxDiffPixels !== void 0 && this.options.maxDiffPixels < 0)
|
||||
throw new Error("`maxDiffPixels` option value must be non-negative integer");
|
||||
if (this.options.maxDiffPixelRatio !== void 0 && (this.options.maxDiffPixelRatio < 0 || this.options.maxDiffPixelRatio > 1))
|
||||
throw new Error("`maxDiffPixelRatio` option value must be between 0 and 1");
|
||||
this.matcherName = matcherName;
|
||||
this.locator = locator;
|
||||
this.updateSnapshots = testInfo.config.updateSnapshots;
|
||||
this.mimeType = import_utilsBundle.mime.getType(import_path.default.basename(this.expectedPath)) ?? "application/octet-stream";
|
||||
this.comparator = (0, import_utils.getComparator)(this.mimeType);
|
||||
this.testInfo = testInfo;
|
||||
this.state = state;
|
||||
this.kind = this.mimeType.startsWith("image/") ? "Screenshot" : "Snapshot";
|
||||
}
|
||||
createMatcherResult(message, pass, log) {
|
||||
const unfiltered = {
|
||||
name: this.matcherName,
|
||||
expected: this.expectedPath,
|
||||
actual: this.actualPath,
|
||||
diff: this.diffPath,
|
||||
pass,
|
||||
message: () => message,
|
||||
log
|
||||
};
|
||||
return Object.fromEntries(Object.entries(unfiltered).filter(([_, v]) => v !== void 0));
|
||||
}
|
||||
handleMissingNegated() {
|
||||
const isWriteMissingMode = this.updateSnapshots !== "none";
|
||||
const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? `, matchers using ".not" won't write them automatically.` : "."}`;
|
||||
return this.createMatcherResult(message, true);
|
||||
}
|
||||
handleDifferentNegated() {
|
||||
return this.createMatcherResult("", false);
|
||||
}
|
||||
handleMatchingNegated() {
|
||||
const message = [
|
||||
import_utils2.colors.red(`${this.kind} comparison failed:`),
|
||||
"",
|
||||
indent("Expected result should be different from the actual one.", " ")
|
||||
].join("\n");
|
||||
return this.createMatcherResult(message, true);
|
||||
}
|
||||
handleMissing(actual, step) {
|
||||
const isWriteMissingMode = this.updateSnapshots !== "none";
|
||||
if (isWriteMissingMode)
|
||||
writeFileSync(this.expectedPath, actual);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
|
||||
writeFileSync(this.actualPath, actual);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
|
||||
const message = `A snapshot doesn't exist at ${this.expectedPath}${isWriteMissingMode ? ", writing actual." : "."}`;
|
||||
if (this.updateSnapshots === "all" || this.updateSnapshots === "changed") {
|
||||
console.log(message);
|
||||
return this.createMatcherResult(message, true);
|
||||
}
|
||||
if (this.updateSnapshots === "missing") {
|
||||
this.testInfo._hasNonRetriableError = true;
|
||||
this.testInfo._failWithError(new Error(message));
|
||||
return this.createMatcherResult("", true);
|
||||
}
|
||||
return this.createMatcherResult(message, false);
|
||||
}
|
||||
handleDifferent(actual, expected, previous, diff, header, diffError, log, step) {
|
||||
const output = [`${header}${indent(diffError, " ")}`];
|
||||
if (this.name) {
|
||||
output.push("");
|
||||
output.push(` Snapshot: ${this.name}`);
|
||||
}
|
||||
if (expected !== void 0) {
|
||||
writeFileSync(this.legacyExpectedPath, expected);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-expected"), contentType: this.mimeType, path: this.expectedPath });
|
||||
}
|
||||
if (previous !== void 0) {
|
||||
writeFileSync(this.previousPath, previous);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-previous"), contentType: this.mimeType, path: this.previousPath });
|
||||
}
|
||||
if (actual !== void 0) {
|
||||
writeFileSync(this.actualPath, actual);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-actual"), contentType: this.mimeType, path: this.actualPath });
|
||||
}
|
||||
if (diff !== void 0) {
|
||||
writeFileSync(this.diffPath, diff);
|
||||
step?._attachToStep({ name: (0, import_util.addSuffixToFilePath)(this.attachmentBaseName, "-diff"), contentType: this.mimeType, path: this.diffPath });
|
||||
}
|
||||
if (log?.length)
|
||||
output.push((0, import_utils.callLogText)(this.state.utils, log));
|
||||
else
|
||||
output.push("");
|
||||
return this.createMatcherResult(output.join("\n"), false, log);
|
||||
}
|
||||
handleMatching() {
|
||||
return this.createMatcherResult("", true);
|
||||
}
|
||||
}
|
||||
function toMatchSnapshot(received, nameOrOptions = {}, optOptions = {}) {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`toMatchSnapshot() must be called during the test`);
|
||||
if (received instanceof Promise)
|
||||
throw new Error("An unresolved Promise was passed to toMatchSnapshot(), make sure to resolve it by adding await to it.");
|
||||
if (testInfo._projectInternal.project.ignoreSnapshots)
|
||||
return { pass: !this.isNot, message: () => "", name: "toMatchSnapshot", expected: nameOrOptions };
|
||||
const configOptions = testInfo._projectInternal.expect?.toMatchSnapshot || {};
|
||||
const helper = new SnapshotHelper(
|
||||
this,
|
||||
testInfo,
|
||||
"toMatchSnapshot",
|
||||
void 0,
|
||||
"." + determineFileExtension(received),
|
||||
configOptions,
|
||||
nameOrOptions,
|
||||
optOptions
|
||||
);
|
||||
if (this.isNot) {
|
||||
if (!import_fs.default.existsSync(helper.expectedPath))
|
||||
return helper.handleMissingNegated();
|
||||
const isDifferent = !!helper.comparator(received, import_fs.default.readFileSync(helper.expectedPath), helper.options);
|
||||
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
||||
}
|
||||
if (!import_fs.default.existsSync(helper.expectedPath))
|
||||
return helper.handleMissing(received, this._stepInfo);
|
||||
const expected = import_fs.default.readFileSync(helper.expectedPath);
|
||||
if (helper.updateSnapshots === "all") {
|
||||
if (!(0, import_utils.compareBuffersOrStrings)(received, expected))
|
||||
return helper.handleMatching();
|
||||
writeFileSync(helper.expectedPath, received);
|
||||
console.log(helper.expectedPath + " is not the same, writing actual.");
|
||||
return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
|
||||
}
|
||||
if (helper.updateSnapshots === "changed") {
|
||||
const result2 = helper.comparator(received, expected, helper.options);
|
||||
if (!result2)
|
||||
return helper.handleMatching();
|
||||
writeFileSync(helper.expectedPath, received);
|
||||
console.log(helper.expectedPath + " does not match, writing actual.");
|
||||
return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
|
||||
}
|
||||
const result = helper.comparator(received, expected, helper.options);
|
||||
if (!result)
|
||||
return helper.handleMatching();
|
||||
const header = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toMatchSnapshot", receiver: (0, import_utils.isString)(received) ? "string" : "Buffer", expectation: "expected" });
|
||||
return helper.handleDifferent(received, expected, void 0, result.diff, header, result.errorMessage, void 0, this._stepInfo);
|
||||
}
|
||||
function toHaveScreenshotStepTitle(nameOrOptions = {}, optOptions = {}) {
|
||||
let name;
|
||||
if (typeof nameOrOptions === "object" && !Array.isArray(nameOrOptions))
|
||||
name = nameOrOptions.name;
|
||||
else
|
||||
name = nameOrOptions;
|
||||
return Array.isArray(name) ? name.join(import_path.default.sep) : name || "";
|
||||
}
|
||||
async function toHaveScreenshot(pageOrLocator, nameOrOptions = {}, optOptions = {}) {
|
||||
const testInfo = (0, import_globals.currentTestInfo)();
|
||||
if (!testInfo)
|
||||
throw new Error(`toHaveScreenshot() must be called during the test`);
|
||||
if (testInfo._projectInternal.project.ignoreSnapshots)
|
||||
return { pass: !this.isNot, message: () => "", name: "toHaveScreenshot", expected: nameOrOptions };
|
||||
(0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
|
||||
const [page, locator] = pageOrLocator.constructor.name === "Page" ? [pageOrLocator, void 0] : [pageOrLocator.page(), pageOrLocator];
|
||||
const configOptions = testInfo._projectInternal.expect?.toHaveScreenshot || {};
|
||||
const helper = new SnapshotHelper(this, testInfo, "toHaveScreenshot", locator, void 0, configOptions, nameOrOptions, optOptions);
|
||||
if (!helper.expectedPath.toLowerCase().endsWith(".png"))
|
||||
throw new Error(`Screenshot name "${import_path.default.basename(helper.expectedPath)}" must have '.png' extension`);
|
||||
(0, import_util.expectTypes)(pageOrLocator, ["Page", "Locator"], "toHaveScreenshot");
|
||||
const style = await loadScreenshotStyles(helper.options.stylePath);
|
||||
const timeout = helper.options.timeout ?? this.timeout;
|
||||
const expectScreenshotOptions = {
|
||||
locator,
|
||||
animations: helper.options.animations ?? "disabled",
|
||||
caret: helper.options.caret ?? "hide",
|
||||
clip: helper.options.clip,
|
||||
fullPage: helper.options.fullPage,
|
||||
mask: helper.options.mask,
|
||||
maskColor: helper.options.maskColor,
|
||||
omitBackground: helper.options.omitBackground,
|
||||
scale: helper.options.scale ?? "css",
|
||||
style,
|
||||
isNot: !!this.isNot,
|
||||
timeout,
|
||||
comparator: helper.options.comparator,
|
||||
maxDiffPixels: helper.options.maxDiffPixels,
|
||||
maxDiffPixelRatio: helper.options.maxDiffPixelRatio,
|
||||
threshold: helper.options.threshold
|
||||
};
|
||||
const hasSnapshot = import_fs.default.existsSync(helper.expectedPath);
|
||||
if (this.isNot) {
|
||||
if (!hasSnapshot)
|
||||
return helper.handleMissingNegated();
|
||||
}
|
||||
if (!this.isNot && helper.updateSnapshots === "none" && !hasSnapshot)
|
||||
return helper.createMatcherResult(`A snapshot doesn't exist at ${helper.expectedPath}.`, false);
|
||||
await page.screencast.hideOverlays();
|
||||
try {
|
||||
if (this.isNot) {
|
||||
expectScreenshotOptions.expected = await import_fs.default.promises.readFile(helper.expectedPath);
|
||||
const isDifferent = !(await page._expectScreenshot(expectScreenshotOptions)).errorMessage;
|
||||
return isDifferent ? helper.handleDifferentNegated() : helper.handleMatchingNegated();
|
||||
}
|
||||
if (!hasSnapshot) {
|
||||
const { actual: actual2, previous: previous2, diff: diff2, errorMessage: errorMessage2, log: log2, timedOut: timedOut2 } = await page._expectScreenshot(expectScreenshotOptions);
|
||||
if (errorMessage2) {
|
||||
const header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut: timedOut2 });
|
||||
return helper.handleDifferent(actual2, void 0, previous2, diff2, header2, errorMessage2, log2, this._stepInfo);
|
||||
}
|
||||
return helper.handleMissing(actual2, this._stepInfo);
|
||||
}
|
||||
const expected = await import_fs.default.promises.readFile(helper.expectedPath);
|
||||
expectScreenshotOptions.expected = helper.updateSnapshots === "all" ? void 0 : expected;
|
||||
const { actual, previous, diff, errorMessage, log, timedOut } = await page._expectScreenshot(expectScreenshotOptions);
|
||||
const writeFiles = (actualBuffer) => {
|
||||
writeFileSync(helper.expectedPath, actualBuffer);
|
||||
writeFileSync(helper.actualPath, actualBuffer);
|
||||
console.log(helper.expectedPath + " is re-generated, writing actual.");
|
||||
return helper.createMatcherResult(helper.expectedPath + " running with --update-snapshots, writing actual.", true);
|
||||
};
|
||||
if (!errorMessage) {
|
||||
if (helper.updateSnapshots === "all" && actual && (0, import_utils.compareBuffersOrStrings)(actual, expected)) {
|
||||
console.log(helper.expectedPath + " is re-generated, writing actual.");
|
||||
return writeFiles(actual);
|
||||
}
|
||||
return helper.handleMatching();
|
||||
}
|
||||
if (helper.updateSnapshots === "changed" || helper.updateSnapshots === "all") {
|
||||
if (actual)
|
||||
return writeFiles(actual);
|
||||
let header2 = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
|
||||
header2 += " Failed to re-generate expected.\n";
|
||||
return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header2, errorMessage, log, this._stepInfo);
|
||||
}
|
||||
const header = (0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, matcherName: "toHaveScreenshot", locator: locator?.toString(), expectation: "expected", timeout, timedOut });
|
||||
return helper.handleDifferent(actual, expectScreenshotOptions.expected, previous, diff, header, errorMessage, log, this._stepInfo);
|
||||
} finally {
|
||||
await page.screencast.showOverlays();
|
||||
}
|
||||
}
|
||||
function writeFileSync(aPath, content) {
|
||||
import_fs.default.mkdirSync(import_path.default.dirname(aPath), { recursive: true });
|
||||
import_fs.default.writeFileSync(aPath, content);
|
||||
}
|
||||
function indent(lines, tab) {
|
||||
return lines.replace(/^(?=.+$)/gm, tab);
|
||||
}
|
||||
function determineFileExtension(file) {
|
||||
if (typeof file === "string")
|
||||
return "txt";
|
||||
if (compareMagicBytes(file, [137, 80, 78, 71, 13, 10, 26, 10]))
|
||||
return "png";
|
||||
if (compareMagicBytes(file, [255, 216, 255]))
|
||||
return "jpg";
|
||||
return "dat";
|
||||
}
|
||||
function compareMagicBytes(file, magicBytes) {
|
||||
return Buffer.compare(Buffer.from(magicBytes), file.slice(0, magicBytes.length)) === 0;
|
||||
}
|
||||
async function loadScreenshotStyles(stylePath) {
|
||||
if (!stylePath)
|
||||
return;
|
||||
const stylePaths = Array.isArray(stylePath) ? stylePath : [stylePath];
|
||||
const styles = await Promise.all(stylePaths.map(async (stylePath2) => {
|
||||
const text = await import_fs.default.promises.readFile(stylePath2, "utf8");
|
||||
return text.trim();
|
||||
}));
|
||||
return styles.join("\n").trim() || void 0;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toHaveScreenshot,
|
||||
toHaveScreenshotStepTitle,
|
||||
toMatchSnapshot
|
||||
});
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var toMatchText_exports = {};
|
||||
__export(toMatchText_exports, {
|
||||
toMatchText: () => toMatchText
|
||||
});
|
||||
module.exports = __toCommonJS(toMatchText_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
async function toMatchText(matcherName, receiver, receiverType, query, expected, options = {}) {
|
||||
(0, import_util.expectTypes)(receiver, [receiverType], matcherName);
|
||||
const locator = receiverType === "Locator" ? receiver : void 0;
|
||||
if (!(typeof expected === "string") && !(expected && typeof expected.test === "function")) {
|
||||
const errorMessage2 = `Error: ${this.utils.EXPECTED_COLOR("expected")} value must be a string or regular expression
|
||||
${this.utils.printWithType("Expected", expected, this.utils.printExpected)}`;
|
||||
throw new Error((0, import_utils.formatMatcherMessage)(this.utils, { promise: this.promise, isNot: this.isNot, locator: locator?.toString(), matcherName, expectation: "expected", errorMessage: errorMessage2 }));
|
||||
}
|
||||
const timeout = options.timeout ?? this.timeout;
|
||||
const { matches: pass, received, log, timedOut, errorMessage } = await query(!!this.isNot, timeout);
|
||||
if (pass === !this.isNot) {
|
||||
return {
|
||||
name: matcherName,
|
||||
message: () => "",
|
||||
pass,
|
||||
expected
|
||||
};
|
||||
}
|
||||
const expectedSuffix = typeof expected === "string" ? options.matchSubstring ? " substring" : "" : " pattern";
|
||||
const receivedSuffix = typeof expected === "string" ? options.matchSubstring ? " string" : "" : " string";
|
||||
const receivedString = received || "";
|
||||
let printedReceived;
|
||||
let printedExpected;
|
||||
let printedDiff;
|
||||
if (pass) {
|
||||
if (typeof expected === "string") {
|
||||
printedExpected = `Expected${expectedSuffix}: not ${this.utils.printExpected(expected)}`;
|
||||
if (!errorMessage) {
|
||||
const formattedReceived = (0, import_utils.printReceivedStringContainExpectedSubstring)(this.utils, receivedString, receivedString.indexOf(expected), expected.length);
|
||||
printedReceived = `Received${receivedSuffix}: ${formattedReceived}`;
|
||||
}
|
||||
} else {
|
||||
printedExpected = `Expected${expectedSuffix}: not ${this.utils.printExpected(expected)}`;
|
||||
if (!errorMessage) {
|
||||
const formattedReceived = (0, import_utils.printReceivedStringContainExpectedResult)(this.utils, receivedString, typeof expected.exec === "function" ? expected.exec(receivedString) : null);
|
||||
printedReceived = `Received${receivedSuffix}: ${formattedReceived}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (errorMessage)
|
||||
printedExpected = `Expected${expectedSuffix}: ${this.utils.printExpected(expected)}`;
|
||||
else
|
||||
printedDiff = this.utils.printDiffOrStringify(expected, receivedString, `Expected${expectedSuffix}`, `Received${receivedSuffix}`, false);
|
||||
}
|
||||
const message = () => {
|
||||
return (0, import_utils.formatMatcherMessage)(this.utils, {
|
||||
promise: this.promise,
|
||||
isNot: this.isNot,
|
||||
matcherName,
|
||||
expectation: "expected",
|
||||
locator: locator?.toString(),
|
||||
timeout,
|
||||
timedOut,
|
||||
printedExpected,
|
||||
printedReceived,
|
||||
printedDiff,
|
||||
log,
|
||||
errorMessage
|
||||
});
|
||||
};
|
||||
return {
|
||||
name: matcherName,
|
||||
expected,
|
||||
message,
|
||||
pass,
|
||||
actual: received,
|
||||
log,
|
||||
timeout: timedOut ? timeout : void 0
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
toMatchText
|
||||
});
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var browserBackend_exports = {};
|
||||
__export(browserBackend_exports, {
|
||||
createCustomMessageHandler: () => createCustomMessageHandler,
|
||||
runDaemonForContext: () => runDaemonForContext
|
||||
});
|
||||
module.exports = __toCommonJS(browserBackend_exports);
|
||||
var import_crypto = __toESM(require("crypto"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
function createCustomMessageHandler(testInfo, context) {
|
||||
let backend;
|
||||
const config = { capabilities: ["testing"] };
|
||||
let tools;
|
||||
return async (data) => {
|
||||
if (!tools)
|
||||
tools = await import("playwright-core/lib/tools/exports");
|
||||
const toolList = tools.filteredTools(config);
|
||||
if (data.initialize) {
|
||||
if (backend)
|
||||
throw new Error("MCP backend is already initialized");
|
||||
backend = new tools.BrowserBackend(config, context, toolList);
|
||||
await backend.initialize(data.initialize.clientInfo);
|
||||
const pausedMessage = await generatePausedMessage(tools, testInfo, context);
|
||||
return { initialize: { pausedMessage } };
|
||||
}
|
||||
if (data.callTool) {
|
||||
if (!backend)
|
||||
throw new Error("MCP backend is not initialized");
|
||||
return { callTool: await backend.callTool(data.callTool.name, data.callTool.arguments) };
|
||||
}
|
||||
if (data.close) {
|
||||
await backend?.dispose();
|
||||
backend = void 0;
|
||||
return { close: {} };
|
||||
}
|
||||
throw new Error("Unknown MCP request");
|
||||
};
|
||||
}
|
||||
async function generatePausedMessage(tools, testInfo, context) {
|
||||
const lines = [];
|
||||
if (testInfo.errors.length) {
|
||||
lines.push(`### Paused on error:`);
|
||||
for (const error of testInfo.errors)
|
||||
lines.push((0, import_utils.stripAnsiEscapes)(error.message || ""));
|
||||
} else {
|
||||
lines.push(`### Paused at end of test. ready for interaction`);
|
||||
}
|
||||
for (let i = 0; i < context.pages().length; i++) {
|
||||
const page = context.pages()[i];
|
||||
const stateSuffix = context.pages().length > 1 ? i + 1 + " of " + context.pages().length : "state";
|
||||
lines.push(
|
||||
"",
|
||||
`### Page ${stateSuffix}`,
|
||||
`- Page URL: ${page.url()}`,
|
||||
`- Page Title: ${await page.title()}`.trim()
|
||||
);
|
||||
let console2 = testInfo.errors.length ? await tools.Tab.collectConsoleMessages(page) : [];
|
||||
console2 = console2.filter((msg) => msg.type === "error");
|
||||
if (console2.length) {
|
||||
lines.push("- Console Messages:");
|
||||
for (const message of console2)
|
||||
lines.push(` - ${message.toString()}`);
|
||||
}
|
||||
lines.push(
|
||||
`- Page Snapshot:`,
|
||||
"```yaml",
|
||||
await page.ariaSnapshot({ mode: "ai" }),
|
||||
"```"
|
||||
);
|
||||
}
|
||||
lines.push("");
|
||||
if (testInfo.errors.length)
|
||||
lines.push(`### Task`, `Try recovering from the error prior to continuing`);
|
||||
return lines.join("\n");
|
||||
}
|
||||
async function runDaemonForContext(testInfo, context) {
|
||||
if (testInfo._configInternal.configCLIOverrides.debug !== "cli")
|
||||
return false;
|
||||
const sessionName = `tw-${import_crypto.default.randomBytes(3).toString("hex")}`;
|
||||
await context.browser().bind(sessionName, { workspaceDir: testInfo.project.testDir });
|
||||
console.log([
|
||||
`### The test is currently paused at the start`,
|
||||
``,
|
||||
`### Debugging Instructions`,
|
||||
`- Run "playwright-cli attach ${sessionName}" to attach to this test`
|
||||
].join("\n"));
|
||||
await context.debugger.requestPause();
|
||||
return true;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
createCustomMessageHandler,
|
||||
runDaemonForContext
|
||||
});
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var generatorTools_exports = {};
|
||||
__export(generatorTools_exports, {
|
||||
generatorReadLog: () => generatorReadLog,
|
||||
generatorWriteTest: () => generatorWriteTest,
|
||||
setupPage: () => setupPage
|
||||
});
|
||||
module.exports = __toCommonJS(generatorTools_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_zodBundle = require("playwright-core/lib/zodBundle");
|
||||
var import_testTool = require("./testTool");
|
||||
var import_testContext = require("./testContext");
|
||||
const setupPage = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "generator_setup_page",
|
||||
title: "Setup generator page",
|
||||
description: "Setup the page for test.",
|
||||
inputSchema: import_zodBundle.z.object({
|
||||
plan: import_zodBundle.z.string().describe("The plan for the test. This should be the actual test plan with all the steps."),
|
||||
project: import_zodBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
|
||||
seedFile: import_zodBundle.z.string().optional().describe('A seed file contains a single test that is used to setup the page for testing, for example: "tests/seed.spec.ts". If no seed file is provided, a default seed file is created.')
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const seed = await context.getOrCreateSeedFile(params.seedFile, params.project);
|
||||
context.generatorJournal = new import_testContext.GeneratorJournal(context.rootPath, params.plan, seed);
|
||||
const { output, status } = await context.runSeedTest(seed.file, seed.projectName);
|
||||
return { content: [{ type: "text", text: output }], isError: status !== "paused" };
|
||||
}
|
||||
});
|
||||
const generatorReadLog = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "generator_read_log",
|
||||
title: "Retrieve test log",
|
||||
description: "Retrieve the performed test log",
|
||||
inputSchema: import_zodBundle.z.object({}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context) => {
|
||||
if (!context.generatorJournal)
|
||||
throw new Error(`Please setup page using "${setupPage.schema.name}" first.`);
|
||||
const result = context.generatorJournal.journal();
|
||||
return { content: [{
|
||||
type: "text",
|
||||
text: result
|
||||
}] };
|
||||
}
|
||||
});
|
||||
const generatorWriteTest = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "generator_write_test",
|
||||
title: "Write test",
|
||||
description: "Write the generated test to the test file",
|
||||
inputSchema: import_zodBundle.z.object({
|
||||
fileName: import_zodBundle.z.string().describe("The file to write the test to"),
|
||||
code: import_zodBundle.z.string().describe("The generated test code")
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
if (!context.generatorJournal)
|
||||
throw new Error(`Please setup page using "${setupPage.schema.name}" first.`);
|
||||
const testRunner = context.existingTestRunner();
|
||||
if (!testRunner)
|
||||
throw new Error("No test runner found, please setup page and perform actions first.");
|
||||
const config = await testRunner.loadConfig();
|
||||
const dirs = [];
|
||||
for (const project of config.projects) {
|
||||
const testDir = import_path.default.relative(context.rootPath, project.project.testDir).replace(/\\/g, "/");
|
||||
const fileName = params.fileName.replace(/\\/g, "/");
|
||||
if (fileName.startsWith(testDir)) {
|
||||
const resolvedFile = import_path.default.resolve(context.rootPath, fileName);
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(resolvedFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(resolvedFile, params.code);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `### Result
|
||||
Test written to ${params.fileName}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
dirs.push(testDir);
|
||||
}
|
||||
throw new Error(`Test file did not match any of the test dirs: ${dirs.join(", ")}`);
|
||||
}
|
||||
});
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
generatorReadLog,
|
||||
generatorWriteTest,
|
||||
setupPage
|
||||
});
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var plannerTools_exports = {};
|
||||
__export(plannerTools_exports, {
|
||||
saveTestPlan: () => saveTestPlan,
|
||||
setupPage: () => setupPage,
|
||||
submitTestPlan: () => submitTestPlan
|
||||
});
|
||||
module.exports = __toCommonJS(plannerTools_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_zodBundle = require("playwright-core/lib/zodBundle");
|
||||
var import_testTool = require("./testTool");
|
||||
const setupPage = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "planner_setup_page",
|
||||
title: "Setup planner page",
|
||||
description: "Setup the page for test planning",
|
||||
inputSchema: import_zodBundle.z.object({
|
||||
project: import_zodBundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
|
||||
seedFile: import_zodBundle.z.string().optional().describe('A seed file contains a single test that is used to setup the page for testing, for example: "tests/seed.spec.ts". If no seed file is provided, a default seed file is created.')
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const seed = await context.getOrCreateSeedFile(params.seedFile, params.project);
|
||||
const { output, status } = await context.runSeedTest(seed.file, seed.projectName);
|
||||
return { content: [{ type: "text", text: output }], isError: status !== "paused" };
|
||||
}
|
||||
});
|
||||
const planSchema = import_zodBundle.z.object({
|
||||
overview: import_zodBundle.z.string().describe("A brief overview of the application to be tested"),
|
||||
suites: import_zodBundle.z.array(import_zodBundle.z.object({
|
||||
name: import_zodBundle.z.string().describe("The name of the suite"),
|
||||
seedFile: import_zodBundle.z.string().describe("A seed file that was used to setup the page for testing."),
|
||||
tests: import_zodBundle.z.array(import_zodBundle.z.object({
|
||||
name: import_zodBundle.z.string().describe("The name of the test"),
|
||||
file: import_zodBundle.z.string().describe('The file the test should be saved to, for example: "tests/<suite-name>/<test-name>.spec.ts".'),
|
||||
steps: import_zodBundle.z.array(import_zodBundle.z.object({
|
||||
perform: import_zodBundle.z.string().optional().describe(`Action to perform. For example: 'Click on the "Submit" button'.`),
|
||||
expect: import_zodBundle.z.string().array().describe(`Expected result of the action where appropriate. For example: 'The page should show the "Thank you for your submission" message'`)
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
});
|
||||
const submitTestPlan = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "planner_submit_plan",
|
||||
title: "Submit test plan",
|
||||
description: "Submit the test plan to the test planner",
|
||||
inputSchema: planSchema,
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(params, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
});
|
||||
const saveTestPlan = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "planner_save_plan",
|
||||
title: "Save test plan as markdown file",
|
||||
description: "Save the test plan as a markdown file",
|
||||
inputSchema: planSchema.extend({
|
||||
name: import_zodBundle.z.string().describe('The name of the test plan, for example: "Test Plan".'),
|
||||
fileName: import_zodBundle.z.string().describe('The file to save the test plan to, for example: "spec/test.plan.md". Relative to the workspace root.')
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const lines = [];
|
||||
lines.push(`# ${params.name}`);
|
||||
lines.push(``);
|
||||
lines.push(`## Application Overview`);
|
||||
lines.push(``);
|
||||
lines.push(params.overview);
|
||||
lines.push(``);
|
||||
lines.push(`## Test Scenarios`);
|
||||
for (let i = 0; i < params.suites.length; i++) {
|
||||
lines.push(``);
|
||||
const suite = params.suites[i];
|
||||
lines.push(`### ${i + 1}. ${suite.name}`);
|
||||
lines.push(``);
|
||||
lines.push(`**Seed:** \`${suite.seedFile}\``);
|
||||
for (let j = 0; j < suite.tests.length; j++) {
|
||||
lines.push(``);
|
||||
const test = suite.tests[j];
|
||||
lines.push(`#### ${i + 1}.${j + 1}. ${test.name}`);
|
||||
lines.push(``);
|
||||
lines.push(`**File:** \`${test.file}\``);
|
||||
lines.push(``);
|
||||
lines.push(`**Steps:**`);
|
||||
for (let k = 0; k < test.steps.length; k++) {
|
||||
lines.push(` ${k + 1}. ${test.steps[k].perform ?? "-"}`);
|
||||
for (const expect of test.steps[k].expect)
|
||||
lines.push(` - expect: ${expect}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.push(``);
|
||||
await import_fs.default.promises.writeFile(import_path.default.resolve(context.rootPath, params.fileName), lines.join("\n"));
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Test plan saved to ${params.fileName}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
});
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
saveTestPlan,
|
||||
setupPage,
|
||||
submitTestPlan
|
||||
});
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var seed_exports = {};
|
||||
__export(seed_exports, {
|
||||
defaultSeedFile: () => defaultSeedFile,
|
||||
ensureSeedFile: () => ensureSeedFile,
|
||||
findSeedFile: () => findSeedFile,
|
||||
seedFileContent: () => seedFileContent,
|
||||
seedProject: () => seedProject
|
||||
});
|
||||
module.exports = __toCommonJS(seed_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_projectUtils = require("../../runner/projectUtils");
|
||||
function seedProject(config, projectName) {
|
||||
if (!projectName)
|
||||
return (0, import_projectUtils.findTopLevelProjects)(config)[0];
|
||||
const project = config.projects.find((p) => p.project.name === projectName);
|
||||
if (!project)
|
||||
throw new Error(`Project ${projectName} not found`);
|
||||
return project;
|
||||
}
|
||||
async function findSeedFile(project) {
|
||||
const files = await (0, import_projectUtils.collectFilesForProject)(project);
|
||||
return files.find((file) => import_path.default.basename(file).includes("seed"));
|
||||
}
|
||||
function defaultSeedFile(project) {
|
||||
const testDir = project.project.testDir;
|
||||
return import_path.default.resolve(testDir, "seed.spec.ts");
|
||||
}
|
||||
async function ensureSeedFile(project) {
|
||||
const seedFile = await findSeedFile(project);
|
||||
if (seedFile)
|
||||
return seedFile;
|
||||
const seedFilePath = defaultSeedFile(project);
|
||||
await (0, import_utils.mkdirIfNeeded)(seedFilePath);
|
||||
await import_fs.default.promises.writeFile(seedFilePath, seedFileContent);
|
||||
return seedFilePath;
|
||||
}
|
||||
const seedFileContent = `import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Test group', () => {
|
||||
test('seed', async ({ page }) => {
|
||||
// generate code here.
|
||||
});
|
||||
});
|
||||
`;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
defaultSeedFile,
|
||||
ensureSeedFile,
|
||||
findSeedFile,
|
||||
seedFileContent,
|
||||
seedProject
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var streams_exports = {};
|
||||
__export(streams_exports, {
|
||||
StringWriteStream: () => StringWriteStream
|
||||
});
|
||||
module.exports = __toCommonJS(streams_exports);
|
||||
var import_stream = require("stream");
|
||||
var import_util = require("../../util");
|
||||
class StringWriteStream extends import_stream.Writable {
|
||||
constructor(output, stdio) {
|
||||
super();
|
||||
this._output = output;
|
||||
this._prefix = stdio === "stdout" ? "" : "[err] ";
|
||||
}
|
||||
_write(chunk, encoding, callback) {
|
||||
let text = (0, import_util.stripAnsiEscapes)(chunk.toString());
|
||||
if (text.endsWith("\n"))
|
||||
text = text.slice(0, -1);
|
||||
if (text)
|
||||
this._output.push(this._prefix + text);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
StringWriteStream
|
||||
});
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testBackend_exports = {};
|
||||
__export(testBackend_exports, {
|
||||
TestServerBackend: () => TestServerBackend,
|
||||
testServerBackendTools: () => testServerBackendTools
|
||||
});
|
||||
module.exports = __toCommonJS(testBackend_exports);
|
||||
var import_events = __toESM(require("events"));
|
||||
var import_zodBundle = require("playwright-core/lib/zodBundle");
|
||||
var import_exports = require("playwright-core/lib/tools/exports");
|
||||
var import_testContext = require("./testContext");
|
||||
var testTools = __toESM(require("./testTools.js"));
|
||||
var generatorTools = __toESM(require("./generatorTools.js"));
|
||||
var plannerTools = __toESM(require("./plannerTools.js"));
|
||||
const typesWithIntent = ["action", "assertion", "input"];
|
||||
const testServerBackendTools = [
|
||||
plannerTools.saveTestPlan,
|
||||
plannerTools.setupPage,
|
||||
plannerTools.submitTestPlan,
|
||||
generatorTools.setupPage,
|
||||
generatorTools.generatorReadLog,
|
||||
generatorTools.generatorWriteTest,
|
||||
testTools.listTests,
|
||||
testTools.runTests,
|
||||
testTools.debugTest,
|
||||
...import_exports.browserTools.map((tool) => wrapBrowserTool(tool))
|
||||
];
|
||||
class TestServerBackend extends import_events.default {
|
||||
constructor(configPath, options) {
|
||||
super();
|
||||
this.name = "Playwright";
|
||||
this.version = "0.0.1";
|
||||
this._options = options || {};
|
||||
this._configPath = configPath;
|
||||
}
|
||||
async initialize(clientInfo) {
|
||||
this._context = new import_testContext.TestContext(clientInfo, this._configPath, this._options);
|
||||
}
|
||||
async callTool(name, args) {
|
||||
const tool = testServerBackendTools.find((tool2) => tool2.schema.name === name);
|
||||
if (!tool)
|
||||
throw new Error(`Tool not found: ${name}. Available tools: ${testServerBackendTools.map((tool2) => tool2.schema.name).join(", ")}`);
|
||||
try {
|
||||
return await tool.handle(this._context, tool.schema.inputSchema.parse(args || {}));
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: String(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
async dispose() {
|
||||
await this._context?.close();
|
||||
}
|
||||
}
|
||||
function wrapBrowserTool(tool) {
|
||||
const inputSchema = typesWithIntent.includes(tool.schema.type) ? tool.schema.inputSchema.extend({
|
||||
intent: import_zodBundle.z.string().describe("The intent of the call, for example the test step description plan idea")
|
||||
}) : tool.schema.inputSchema;
|
||||
return {
|
||||
schema: {
|
||||
...tool.schema,
|
||||
inputSchema
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const response = await context.sendMessageToPausedTest({ callTool: { name: tool.schema.name, arguments: params } });
|
||||
return response.callTool;
|
||||
}
|
||||
};
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TestServerBackend,
|
||||
testServerBackendTools
|
||||
});
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testContext_exports = {};
|
||||
__export(testContext_exports, {
|
||||
GeneratorJournal: () => GeneratorJournal,
|
||||
TestContext: () => TestContext,
|
||||
createScreen: () => createScreen
|
||||
});
|
||||
module.exports = __toCommonJS(testContext_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_os = __toESM(require("os"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_exports = require("playwright-core/lib/tools/exports");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_base = require("../../reporters/base");
|
||||
var import_list = __toESM(require("../../reporters/list"));
|
||||
var import_streams = require("./streams");
|
||||
var import_util = require("../../util");
|
||||
var import_testRunner = require("../../runner/testRunner");
|
||||
var import_seed = require("./seed");
|
||||
var import_configLoader = require("../../common/configLoader");
|
||||
class GeneratorJournal {
|
||||
constructor(rootPath, plan, seed) {
|
||||
this._rootPath = rootPath;
|
||||
this._plan = plan;
|
||||
this._seed = seed;
|
||||
this._steps = [];
|
||||
}
|
||||
logStep(title, code) {
|
||||
if (title)
|
||||
this._steps.push({ title, code });
|
||||
}
|
||||
journal() {
|
||||
const result = [];
|
||||
result.push(`# Plan`);
|
||||
result.push(this._plan);
|
||||
result.push(`# Seed file: ${(0, import_utils.toPosixPath)(import_path.default.relative(this._rootPath, this._seed.file))}`);
|
||||
result.push("```ts");
|
||||
result.push(this._seed.content);
|
||||
result.push("```");
|
||||
result.push(`# Steps`);
|
||||
result.push(this._steps.map((step) => `### ${step.title}
|
||||
\`\`\`ts
|
||||
${step.code}
|
||||
\`\`\``).join("\n\n"));
|
||||
result.push(bestPracticesMarkdown);
|
||||
return result.join("\n\n");
|
||||
}
|
||||
}
|
||||
class TestContext {
|
||||
constructor(clientInfo, configPath, options) {
|
||||
this._clientInfo = clientInfo;
|
||||
this._configLocation = (0, import_configLoader.resolveConfigLocation)(configPath || clientInfo.cwd);
|
||||
this.rootPath = clientInfo.cwd || this._configLocation.configDir;
|
||||
if (options?.headless !== void 0)
|
||||
this.computedHeaded = !options.headless;
|
||||
else
|
||||
this.computedHeaded = !process.env.CI && !(import_os.default.platform() === "linux" && !process.env.DISPLAY);
|
||||
}
|
||||
existingTestRunner() {
|
||||
return this._testRunnerAndScreen?.testRunner;
|
||||
}
|
||||
async _cleanupTestRunner() {
|
||||
if (!this._testRunnerAndScreen)
|
||||
return;
|
||||
await this._testRunnerAndScreen.testRunner.stopTests();
|
||||
this._testRunnerAndScreen.claimStdio();
|
||||
try {
|
||||
await this._testRunnerAndScreen.testRunner.runGlobalTeardown();
|
||||
} finally {
|
||||
this._testRunnerAndScreen.releaseStdio();
|
||||
this._testRunnerAndScreen = void 0;
|
||||
}
|
||||
}
|
||||
async createTestRunner() {
|
||||
await this._cleanupTestRunner();
|
||||
const testRunner = new import_testRunner.TestRunner(this._configLocation, {});
|
||||
await testRunner.initialize({});
|
||||
const testPaused = new import_utils.ManualPromise();
|
||||
const testRunnerAndScreen = {
|
||||
...createScreen(),
|
||||
testRunner,
|
||||
waitForTestPaused: () => testPaused
|
||||
};
|
||||
this._testRunnerAndScreen = testRunnerAndScreen;
|
||||
testRunner.on(import_testRunner.TestRunnerEvent.TestPaused, (params) => {
|
||||
testRunnerAndScreen.sendMessageToPausedTest = params.sendMessage;
|
||||
testPaused.resolve();
|
||||
});
|
||||
return testRunnerAndScreen;
|
||||
}
|
||||
async getOrCreateSeedFile(seedFile, projectName) {
|
||||
const configDir = this._configLocation.configDir;
|
||||
const { testRunner } = await this.createTestRunner();
|
||||
const config = await testRunner.loadConfig();
|
||||
const project = (0, import_seed.seedProject)(config, projectName);
|
||||
if (!seedFile) {
|
||||
seedFile = await (0, import_seed.ensureSeedFile)(project);
|
||||
} else {
|
||||
const candidateFiles = [];
|
||||
const testDir = project.project.testDir;
|
||||
candidateFiles.push(import_path.default.resolve(testDir, seedFile));
|
||||
candidateFiles.push(import_path.default.resolve(configDir, seedFile));
|
||||
candidateFiles.push(import_path.default.resolve(this.rootPath, seedFile));
|
||||
let resolvedSeedFile;
|
||||
for (const candidateFile of candidateFiles) {
|
||||
if (await (0, import_util.fileExistsAsync)(candidateFile)) {
|
||||
resolvedSeedFile = candidateFile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!resolvedSeedFile)
|
||||
throw new Error("seed test not found.");
|
||||
seedFile = resolvedSeedFile;
|
||||
}
|
||||
const seedFileContent = await import_fs.default.promises.readFile(seedFile, "utf8");
|
||||
return {
|
||||
file: seedFile,
|
||||
content: seedFileContent,
|
||||
projectName: project.project.name
|
||||
};
|
||||
}
|
||||
async runSeedTest(seedFile, projectName) {
|
||||
const result = await this.runTestsWithGlobalSetupAndPossiblePause({
|
||||
headed: this.computedHeaded,
|
||||
locations: ["/" + (0, import_utils.escapeRegExp)(seedFile) + "/"],
|
||||
projects: [projectName],
|
||||
timeout: 0,
|
||||
workers: 1,
|
||||
pauseAtEnd: true,
|
||||
disableConfigReporters: true,
|
||||
failOnLoadErrors: true
|
||||
});
|
||||
if (result.status === "passed")
|
||||
result.output += "\nError: seed test not found.";
|
||||
else if (result.status !== "paused")
|
||||
result.output += "\nError while running the seed test.";
|
||||
return result;
|
||||
}
|
||||
async runTestsWithGlobalSetupAndPossiblePause(params) {
|
||||
const configDir = this._configLocation.configDir;
|
||||
const testRunnerAndScreen = await this.createTestRunner();
|
||||
const { testRunner, screen, claimStdio, releaseStdio } = testRunnerAndScreen;
|
||||
claimStdio();
|
||||
try {
|
||||
const setupReporter = new MCPListReporter({ configDir, screen, includeTestId: true });
|
||||
const { status: status2 } = await testRunner.runGlobalSetup([setupReporter]);
|
||||
if (status2 !== "passed")
|
||||
return { output: testRunnerAndScreen.output.join("\n"), status: status2 };
|
||||
} finally {
|
||||
releaseStdio();
|
||||
}
|
||||
let status = "passed";
|
||||
const cleanup = async () => {
|
||||
claimStdio();
|
||||
try {
|
||||
const result = await testRunner.runGlobalTeardown();
|
||||
if (status === "passed")
|
||||
status = result.status;
|
||||
} finally {
|
||||
releaseStdio();
|
||||
}
|
||||
};
|
||||
try {
|
||||
const reporter = new MCPListReporter({ configDir, screen, includeTestId: true });
|
||||
status = await Promise.race([
|
||||
testRunner.runTests(reporter, params).then((result) => result.status),
|
||||
testRunnerAndScreen.waitForTestPaused().then(() => "paused")
|
||||
]);
|
||||
if (status === "paused") {
|
||||
const response = await testRunnerAndScreen.sendMessageToPausedTest({ request: { initialize: { clientInfo: this._clientInfo } } });
|
||||
if (response.error)
|
||||
throw new Error(response.error.message);
|
||||
testRunnerAndScreen.output.push(response.response.initialize.pausedMessage);
|
||||
return { output: testRunnerAndScreen.output.join("\n"), status };
|
||||
}
|
||||
} catch (e) {
|
||||
status = "failed";
|
||||
testRunnerAndScreen.output.push(String(e));
|
||||
await cleanup();
|
||||
return { output: testRunnerAndScreen.output.join("\n"), status };
|
||||
}
|
||||
await cleanup();
|
||||
return { output: testRunnerAndScreen.output.join("\n"), status };
|
||||
}
|
||||
async close() {
|
||||
await this._cleanupTestRunner().catch((e) => (0, import_utilsBundle.debug)("pw:mcp:error")(e));
|
||||
}
|
||||
async sendMessageToPausedTest(request) {
|
||||
const sendMessage = this._testRunnerAndScreen?.sendMessageToPausedTest;
|
||||
if (!sendMessage)
|
||||
throw new Error("Must setup test before interacting with the page");
|
||||
const result = await sendMessage({ request });
|
||||
if (result.error)
|
||||
throw new Error(result.error.message);
|
||||
if (typeof request?.callTool?.arguments?.["intent"] === "string") {
|
||||
const response = (0, import_exports.parseResponse)(result.response.callTool);
|
||||
if (response && !response.isError && response.code)
|
||||
this.generatorJournal?.logStep(request.callTool.arguments["intent"], response.code);
|
||||
}
|
||||
return result.response;
|
||||
}
|
||||
}
|
||||
function createScreen() {
|
||||
const output = [];
|
||||
const stdout = new import_streams.StringWriteStream(output, "stdout");
|
||||
const stderr = new import_streams.StringWriteStream(output, "stderr");
|
||||
const screen = {
|
||||
...import_base.terminalScreen,
|
||||
isTTY: false,
|
||||
colors: import_utils.noColors,
|
||||
stdout,
|
||||
stderr
|
||||
};
|
||||
const originalStdoutWrite = process.stdout.write;
|
||||
const originalStderrWrite = process.stderr.write;
|
||||
const claimStdio = () => {
|
||||
process.stdout.write = (chunk) => {
|
||||
stdout.write(chunk);
|
||||
return true;
|
||||
};
|
||||
process.stderr.write = (chunk) => {
|
||||
stderr.write(chunk);
|
||||
return true;
|
||||
};
|
||||
};
|
||||
const releaseStdio = () => {
|
||||
process.stdout.write = originalStdoutWrite;
|
||||
process.stderr.write = originalStderrWrite;
|
||||
};
|
||||
return { screen, claimStdio, releaseStdio, output };
|
||||
}
|
||||
const bestPracticesMarkdown = `
|
||||
# Best practices
|
||||
- Do not improvise, do not add directives that were not asked for
|
||||
- Use clear, descriptive assertions to validate the expected behavior
|
||||
- Use reliable locators from this log
|
||||
- Use local variables for locators that are used multiple times
|
||||
- Use Playwright waiting assertions and best practices from this log
|
||||
- NEVER! use page.waitForLoadState()
|
||||
- NEVER! use page.waitForNavigation()
|
||||
- NEVER! use page.waitForTimeout()
|
||||
- NEVER! use page.evaluate()
|
||||
`;
|
||||
class MCPListReporter extends import_list.default {
|
||||
async onTestPaused() {
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
GeneratorJournal,
|
||||
TestContext,
|
||||
createScreen
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testTool_exports = {};
|
||||
__export(testTool_exports, {
|
||||
defineTestTool: () => defineTestTool
|
||||
});
|
||||
module.exports = __toCommonJS(testTool_exports);
|
||||
function defineTestTool(tool) {
|
||||
return tool;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
defineTestTool
|
||||
});
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var testTools_exports = {};
|
||||
__export(testTools_exports, {
|
||||
debugTest: () => debugTest,
|
||||
listTests: () => listTests,
|
||||
runTests: () => runTests
|
||||
});
|
||||
module.exports = __toCommonJS(testTools_exports);
|
||||
var import_zodBundle = require("playwright-core/lib/zodBundle");
|
||||
var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
|
||||
var import_testTool = require("./testTool");
|
||||
const listTests = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "test_list",
|
||||
title: "List tests",
|
||||
description: "List tests",
|
||||
inputSchema: import_zodBundle.z.object({}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context) => {
|
||||
const { testRunner, screen, output } = await context.createTestRunner();
|
||||
const reporter = new import_listModeReporter.default({ screen, includeTestId: true });
|
||||
await testRunner.listTests(reporter, {});
|
||||
return { content: output.map((text) => ({ type: "text", text })) };
|
||||
}
|
||||
});
|
||||
const runTests = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "test_run",
|
||||
title: "Run tests",
|
||||
description: "Run tests",
|
||||
inputSchema: import_zodBundle.z.object({
|
||||
locations: import_zodBundle.z.array(import_zodBundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
|
||||
projects: import_zodBundle.z.array(import_zodBundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const { output } = await context.runTestsWithGlobalSetupAndPossiblePause({
|
||||
locations: params.locations ?? [],
|
||||
projects: params.projects,
|
||||
disableConfigReporters: true
|
||||
});
|
||||
return { content: [{ type: "text", text: output }] };
|
||||
}
|
||||
});
|
||||
const debugTest = (0, import_testTool.defineTestTool)({
|
||||
schema: {
|
||||
name: "test_debug",
|
||||
title: "Debug single test",
|
||||
description: "Debug single test",
|
||||
inputSchema: import_zodBundle.z.object({
|
||||
test: import_zodBundle.z.object({
|
||||
id: import_zodBundle.z.string().describe("Test ID to debug."),
|
||||
title: import_zodBundle.z.string().describe("Human readable test title for granting permission to debug the test.")
|
||||
})
|
||||
}),
|
||||
type: "readOnly"
|
||||
},
|
||||
handle: async (context, params) => {
|
||||
const { output, status } = await context.runTestsWithGlobalSetupAndPossiblePause({
|
||||
headed: context.computedHeaded,
|
||||
locations: [],
|
||||
// we can make this faster by passing the test's location, so we don't need to scan all tests to find the ID
|
||||
testIds: [params.test.id],
|
||||
// For automatic recovery
|
||||
timeout: 0,
|
||||
workers: 1,
|
||||
pauseOnError: true,
|
||||
disableConfigReporters: true,
|
||||
actionTimeout: 5e3
|
||||
});
|
||||
return { content: [{ type: "text", text: output }], isError: status !== "paused" && status !== "passed" };
|
||||
}
|
||||
});
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
debugTest,
|
||||
listTests,
|
||||
runTests
|
||||
});
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var gitCommitInfoPlugin_exports = {};
|
||||
__export(gitCommitInfoPlugin_exports, {
|
||||
addGitCommitInfoPlugin: () => addGitCommitInfoPlugin
|
||||
});
|
||||
module.exports = __toCommonJS(gitCommitInfoPlugin_exports);
|
||||
var fs = __toESM(require("fs"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
const GIT_OPERATIONS_TIMEOUT_MS = 3e3;
|
||||
const addGitCommitInfoPlugin = (fullConfig) => {
|
||||
fullConfig.plugins.push({ factory: gitCommitInfoPlugin.bind(null, fullConfig) });
|
||||
};
|
||||
function print(s, ...args) {
|
||||
console.log("GitCommitInfo: " + s, ...args);
|
||||
}
|
||||
function debug(s, ...args) {
|
||||
if (!process.env.DEBUG_GIT_COMMIT_INFO)
|
||||
return;
|
||||
print(s, ...args);
|
||||
}
|
||||
const gitCommitInfoPlugin = (fullConfig) => {
|
||||
return {
|
||||
name: "playwright:git-commit-info",
|
||||
setup: async (config, configDir) => {
|
||||
const metadata = config.metadata;
|
||||
const ci = await ciInfo();
|
||||
if (!metadata.ci && ci) {
|
||||
debug("ci info", ci);
|
||||
metadata.ci = ci;
|
||||
}
|
||||
if (fullConfig.captureGitInfo?.commit || fullConfig.captureGitInfo?.commit === void 0 && ci) {
|
||||
const git = await gitCommitInfo(configDir).catch((e) => print("failed to get git commit info", e));
|
||||
if (git) {
|
||||
debug("commit info", git);
|
||||
metadata.gitCommit = git;
|
||||
}
|
||||
}
|
||||
if (fullConfig.captureGitInfo?.diff || fullConfig.captureGitInfo?.diff === void 0 && ci) {
|
||||
const diffResult = await gitDiff(configDir, ci).catch((e) => print("failed to get git diff", e));
|
||||
if (diffResult) {
|
||||
debug(`diff length ${diffResult.length}`);
|
||||
metadata.gitDiff = diffResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
async function ciInfo() {
|
||||
if (process.env.GITHUB_ACTIONS) {
|
||||
let pr;
|
||||
try {
|
||||
const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, "utf8"));
|
||||
pr = { title: json.pull_request.title, number: json.pull_request.number, baseHash: json.pull_request.base.sha };
|
||||
} catch {
|
||||
}
|
||||
return {
|
||||
commitHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`,
|
||||
commitHash: process.env.GITHUB_SHA,
|
||||
prHref: pr ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${pr.number}` : void 0,
|
||||
prTitle: pr?.title,
|
||||
prBaseHash: pr?.baseHash,
|
||||
buildHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`
|
||||
};
|
||||
}
|
||||
if (process.env.GITLAB_CI) {
|
||||
return {
|
||||
commitHref: `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`,
|
||||
commitHash: process.env.CI_COMMIT_SHA,
|
||||
buildHref: process.env.CI_JOB_URL,
|
||||
branch: process.env.CI_COMMIT_REF_NAME
|
||||
};
|
||||
}
|
||||
if (process.env.JENKINS_URL && process.env.BUILD_URL) {
|
||||
return {
|
||||
commitHref: process.env.BUILD_URL,
|
||||
commitHash: process.env.GIT_COMMIT,
|
||||
branch: process.env.GIT_BRANCH
|
||||
};
|
||||
}
|
||||
}
|
||||
async function gitCommitInfo(gitDir) {
|
||||
const separator = `---786eec917292---`;
|
||||
const tokens = [
|
||||
"%H",
|
||||
// commit hash
|
||||
"%h",
|
||||
// abbreviated commit hash
|
||||
"%s",
|
||||
// subject
|
||||
"%B",
|
||||
// raw body (unwrapped subject and body)
|
||||
"%an",
|
||||
// author name
|
||||
"%ae",
|
||||
// author email
|
||||
"%at",
|
||||
// author date, UNIX timestamp
|
||||
"%cn",
|
||||
// committer name
|
||||
"%ce",
|
||||
// committer email
|
||||
"%ct",
|
||||
// committer date, UNIX timestamp
|
||||
""
|
||||
// branch
|
||||
];
|
||||
const output = await runGit(`git log -1 --pretty=format:"${tokens.join(separator)}" && git rev-parse --abbrev-ref HEAD`, gitDir);
|
||||
if (!output)
|
||||
return void 0;
|
||||
const [hash, shortHash, subject, body, authorName, authorEmail, authorTime, committerName, committerEmail, committerTime, branch] = output.split(separator);
|
||||
return {
|
||||
shortHash,
|
||||
hash,
|
||||
subject,
|
||||
body,
|
||||
author: {
|
||||
name: authorName,
|
||||
email: authorEmail,
|
||||
time: +authorTime * 1e3
|
||||
},
|
||||
committer: {
|
||||
name: committerName,
|
||||
email: committerEmail,
|
||||
time: +committerTime * 1e3
|
||||
},
|
||||
branch: branch.trim()
|
||||
};
|
||||
}
|
||||
async function gitDiff(gitDir, ci) {
|
||||
const diffLimit = 1e5;
|
||||
if (ci?.prBaseHash) {
|
||||
await runGit(`git fetch origin ${ci.prBaseHash} --depth=1 --no-auto-maintenance --no-auto-gc --no-tags --no-recurse-submodules`, gitDir);
|
||||
const diff2 = await runGit(`git diff ${ci.prBaseHash} HEAD`, gitDir);
|
||||
if (diff2)
|
||||
return diff2.substring(0, diffLimit);
|
||||
}
|
||||
if (ci)
|
||||
return;
|
||||
const uncommitted = await runGit("git diff", gitDir);
|
||||
if (uncommitted === void 0) {
|
||||
return;
|
||||
}
|
||||
if (uncommitted)
|
||||
return uncommitted.substring(0, diffLimit);
|
||||
const diff = await runGit("git diff HEAD~1", gitDir);
|
||||
return diff?.substring(0, diffLimit);
|
||||
}
|
||||
async function runGit(command, cwd) {
|
||||
debug(`running "${command}"`);
|
||||
const start = (0, import_utils.monotonicTime)();
|
||||
const result = await (0, import_utils.spawnAsync)(
|
||||
command,
|
||||
[],
|
||||
{ stdio: "pipe", cwd, timeout: GIT_OPERATIONS_TIMEOUT_MS, shell: true }
|
||||
);
|
||||
if ((0, import_utils.monotonicTime)() - start > GIT_OPERATIONS_TIMEOUT_MS) {
|
||||
print(`timeout of ${GIT_OPERATIONS_TIMEOUT_MS}ms exceeded while running "${command}"`);
|
||||
return;
|
||||
}
|
||||
if (result.code)
|
||||
debug(`failure, code=${result.code}
|
||||
|
||||
${result.stderr}`);
|
||||
else
|
||||
debug(`success`);
|
||||
return result.code ? void 0 : result.stdout.trim();
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
addGitCommitInfoPlugin
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var plugins_exports = {};
|
||||
__export(plugins_exports, {
|
||||
webServer: () => import_webServerPlugin.webServer
|
||||
});
|
||||
module.exports = __toCommonJS(plugins_exports);
|
||||
var import_webServerPlugin = require("./webServerPlugin");
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
webServer
|
||||
});
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var webServerPlugin_exports = {};
|
||||
__export(webServerPlugin_exports, {
|
||||
WebServerPlugin: () => WebServerPlugin,
|
||||
webServer: () => webServer,
|
||||
webServerPluginsForConfig: () => webServerPluginsForConfig
|
||||
});
|
||||
module.exports = __toCommonJS(webServerPlugin_exports);
|
||||
var import_net = __toESM(require("net"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
const DEFAULT_ENVIRONMENT_VARIABLES = {
|
||||
"BROWSER": "none",
|
||||
// Disable that create-react-app will open the page in the browser
|
||||
"FORCE_COLOR": "1",
|
||||
"DEBUG_COLORS": "1"
|
||||
};
|
||||
const debugWebServer = (0, import_utilsBundle.debug)("pw:webserver");
|
||||
class WebServerPlugin {
|
||||
constructor(options, checkPortOnly) {
|
||||
this.name = "playwright:webserver";
|
||||
this._options = options;
|
||||
this._checkPortOnly = checkPortOnly;
|
||||
}
|
||||
async setup(config, configDir, reporter) {
|
||||
this._reporter = reporter;
|
||||
if (this._options.url)
|
||||
this._isAvailableCallback = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter));
|
||||
this._options.cwd = this._options.cwd ? import_path.default.resolve(configDir, this._options.cwd) : configDir;
|
||||
try {
|
||||
await this._startProcess();
|
||||
await this._waitForProcess();
|
||||
} catch (error) {
|
||||
await this.teardown();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async teardown() {
|
||||
debugWebServer(`Terminating the WebServer`);
|
||||
await this._killProcess?.();
|
||||
debugWebServer(`Terminated the WebServer`);
|
||||
}
|
||||
async _startProcess() {
|
||||
let processExitedReject = (error) => {
|
||||
};
|
||||
this._processExitedPromise = new Promise((_, reject) => processExitedReject = reject);
|
||||
const isAlreadyAvailable = await this._isAvailableCallback?.();
|
||||
if (isAlreadyAvailable) {
|
||||
debugWebServer(`WebServer is already available`);
|
||||
if (this._options.reuseExistingServer)
|
||||
return;
|
||||
const port = new URL(this._options.url).port;
|
||||
throw new Error(`${this._options.url ?? `http://localhost${port ? ":" + port : ""}`} is already used, make sure that nothing is running on the port/url or set reuseExistingServer:true in config.webServer.`);
|
||||
}
|
||||
if (!this._options.command)
|
||||
throw new Error("config.webServer.command cannot be empty");
|
||||
debugWebServer(`Starting WebServer process ${this._options.command}...`);
|
||||
const { launchedProcess, gracefullyClose } = await (0, import_utils.launchProcess)({
|
||||
command: this._options.command,
|
||||
env: {
|
||||
...DEFAULT_ENVIRONMENT_VARIABLES,
|
||||
...process.env,
|
||||
...this._options.env
|
||||
},
|
||||
cwd: this._options.cwd,
|
||||
stdio: "stdin",
|
||||
shell: true,
|
||||
attemptToGracefullyClose: async () => {
|
||||
if (process.platform === "win32")
|
||||
throw new Error("Graceful shutdown is not supported on Windows");
|
||||
if (!this._options.gracefulShutdown)
|
||||
throw new Error("skip graceful shutdown");
|
||||
const { signal, timeout = 0 } = this._options.gracefulShutdown;
|
||||
process.kill(-launchedProcess.pid, signal);
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = timeout !== 0 ? setTimeout(() => reject(new Error(`process didn't close gracefully within timeout`)), timeout) : void 0;
|
||||
launchedProcess.once("close", (...args) => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
log: () => {
|
||||
},
|
||||
onExit: (code) => processExitedReject(new Error(code ? `Process from config.webServer was not able to start. Exit code: ${code}` : "Process from config.webServer exited early.")),
|
||||
tempDirectories: []
|
||||
});
|
||||
this._killProcess = gracefullyClose;
|
||||
debugWebServer(`Process started`);
|
||||
if (this._options.wait?.stdout || this._options.wait?.stderr)
|
||||
this._waitForStdioPromise = new import_utils.ManualPromise();
|
||||
const stdioWaitCollectors = {
|
||||
stdout: this._options.wait?.stdout ? "" : void 0,
|
||||
stderr: this._options.wait?.stderr ? "" : void 0
|
||||
};
|
||||
launchedProcess.stdout.on("data", (data) => {
|
||||
if (debugWebServer.enabled || this._options.stdout === "pipe")
|
||||
this._reporter.onStdOut?.(prefixOutputLines(data.toString(), this._options.name));
|
||||
});
|
||||
launchedProcess.stderr.on("data", (data) => {
|
||||
if (debugWebServer.enabled || (this._options.stderr === "pipe" || !this._options.stderr))
|
||||
this._reporter.onStdErr?.(prefixOutputLines(data.toString(), this._options.name));
|
||||
});
|
||||
const resolveStdioPromise = () => {
|
||||
stdioWaitCollectors.stdout = void 0;
|
||||
stdioWaitCollectors.stderr = void 0;
|
||||
this._waitForStdioPromise?.resolve();
|
||||
};
|
||||
for (const stdio of ["stdout", "stderr"]) {
|
||||
launchedProcess[stdio].on("data", (data) => {
|
||||
if (!this._options.wait?.[stdio] || stdioWaitCollectors[stdio] === void 0)
|
||||
return;
|
||||
stdioWaitCollectors[stdio] += data.toString();
|
||||
this._options.wait[stdio].lastIndex = 0;
|
||||
const result = this._options.wait[stdio].exec(stdioWaitCollectors[stdio]);
|
||||
if (result) {
|
||||
for (const [key, value] of Object.entries(result.groups || {}))
|
||||
process.env[key.toUpperCase()] = value;
|
||||
resolveStdioPromise();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
async _waitForProcess() {
|
||||
if (!this._isAvailableCallback && !this._waitForStdioPromise) {
|
||||
this._processExitedPromise.catch(() => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
debugWebServer(`Waiting for availability...`);
|
||||
const launchTimeout = this._options.timeout || 60 * 1e3;
|
||||
const cancellationToken = { canceled: false };
|
||||
const deadline = (0, import_utils.monotonicTime)() + launchTimeout;
|
||||
const racingPromises = [this._processExitedPromise];
|
||||
if (this._isAvailableCallback)
|
||||
racingPromises.push((0, import_utils.raceAgainstDeadline)(() => waitFor(this._isAvailableCallback, cancellationToken), deadline));
|
||||
if (this._waitForStdioPromise)
|
||||
racingPromises.push((0, import_utils.raceAgainstDeadline)(() => this._waitForStdioPromise, deadline));
|
||||
const { timedOut } = await Promise.race(racingPromises);
|
||||
cancellationToken.canceled = true;
|
||||
if (timedOut)
|
||||
throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`);
|
||||
debugWebServer(`WebServer available`);
|
||||
}
|
||||
}
|
||||
async function isPortUsed(port) {
|
||||
const innerIsPortUsed = (host) => new Promise((resolve) => {
|
||||
const conn = import_net.default.connect(port, host).on("error", () => {
|
||||
resolve(false);
|
||||
}).on("connect", () => {
|
||||
conn.end();
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
return await innerIsPortUsed("127.0.0.1") || await innerIsPortUsed("::1");
|
||||
}
|
||||
async function waitFor(waitFn, cancellationToken) {
|
||||
const logScale = [100, 250, 500];
|
||||
while (!cancellationToken.canceled) {
|
||||
const connected = await waitFn();
|
||||
if (connected)
|
||||
return;
|
||||
const delay = logScale.shift() || 1e3;
|
||||
debugWebServer(`Waiting ${delay}ms`);
|
||||
await new Promise((x) => setTimeout(x, delay));
|
||||
}
|
||||
}
|
||||
function getIsAvailableFunction(url, checkPortOnly, ignoreHTTPSErrors, onStdErr) {
|
||||
const urlObject = new URL(url);
|
||||
if (!checkPortOnly)
|
||||
return () => (0, import_utils.isURLAvailable)(urlObject, ignoreHTTPSErrors, debugWebServer, onStdErr);
|
||||
const port = urlObject.port;
|
||||
return () => isPortUsed(+port);
|
||||
}
|
||||
const webServer = (options) => {
|
||||
return new WebServerPlugin(options, false);
|
||||
};
|
||||
const webServerPluginsForConfig = (config) => {
|
||||
const shouldSetBaseUrl = !!config.config.webServer;
|
||||
const webServerPlugins = [];
|
||||
for (const webServerConfig of config.webServers) {
|
||||
if (webServerConfig.port && webServerConfig.url)
|
||||
throw new Error(`Either 'port' or 'url' should be specified in config.webServer.`);
|
||||
let url;
|
||||
if (webServerConfig.port || webServerConfig.url) {
|
||||
url = webServerConfig.url || `http://localhost:${webServerConfig.port}`;
|
||||
if (shouldSetBaseUrl && !webServerConfig.url)
|
||||
process.env.PLAYWRIGHT_TEST_BASE_URL = url;
|
||||
}
|
||||
webServerPlugins.push(new WebServerPlugin({ ...webServerConfig, url }, webServerConfig.port !== void 0));
|
||||
}
|
||||
return webServerPlugins;
|
||||
};
|
||||
function prefixOutputLines(output, prefixName = "WebServer") {
|
||||
const lastIsNewLine = output[output.length - 1] === "\n";
|
||||
let lines = output.split("\n");
|
||||
if (lastIsNewLine)
|
||||
lines.pop();
|
||||
lines = lines.map((line) => import_utils2.colors.dim(`[${prefixName}] `) + line);
|
||||
if (lastIsNewLine)
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
WebServerPlugin,
|
||||
webServer,
|
||||
webServerPluginsForConfig
|
||||
});
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var program_exports = {};
|
||||
__export(program_exports, {
|
||||
program: () => import_program2.program
|
||||
});
|
||||
module.exports = __toCommonJS(program_exports);
|
||||
var import_bootstrap = require("playwright-core/lib/bootstrap");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_program = require("playwright-core/lib/cli/program");
|
||||
var import_config = require("./common/config");
|
||||
var import_program2 = require("playwright-core/lib/cli/program");
|
||||
const packageJSON = require("../package.json");
|
||||
function addTestCommand(program3) {
|
||||
const command = program3.command("test [test-filter...]");
|
||||
command.description("run tests with Playwright Test");
|
||||
const options = testOptions.sort((a, b) => a[0].replace(/-/g, "").localeCompare(b[0].replace(/-/g, "")));
|
||||
options.forEach(([name, { description, choices, preset }]) => {
|
||||
const option = command.createOption(name, description);
|
||||
if (choices)
|
||||
option.choices(choices);
|
||||
if (preset)
|
||||
option.preset(preset);
|
||||
command.addOption(option);
|
||||
return command;
|
||||
});
|
||||
command.action(async (args, opts) => {
|
||||
try {
|
||||
const { runTests } = require("./testActions");
|
||||
await runTests(args, opts);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
|
||||
}
|
||||
});
|
||||
command.addHelpText("afterAll", `
|
||||
Arguments [test-filter...]:
|
||||
Pass arguments to filter test files. Each argument is treated as a regular expression. Matching is performed against the absolute file paths.
|
||||
|
||||
Examples:
|
||||
$ npx playwright test my.spec.ts
|
||||
$ npx playwright test some.spec.ts:42
|
||||
$ npx playwright test --headed
|
||||
$ npx playwright test --project=webkit`);
|
||||
}
|
||||
function addClearCacheCommand(program3) {
|
||||
const command = program3.command("clear-cache");
|
||||
command.description("clears build and test caches");
|
||||
command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||
command.action(async (opts) => {
|
||||
const { clearCache } = require("./testActions");
|
||||
await clearCache(opts);
|
||||
});
|
||||
}
|
||||
function addDevServerCommand(program3) {
|
||||
const command = program3.command("dev-server", { hidden: true });
|
||||
command.description("start dev server");
|
||||
command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||
command.action(async (options) => {
|
||||
const { startDevServer } = require("./testActions");
|
||||
await startDevServer(options);
|
||||
});
|
||||
}
|
||||
function addTestServerCommand(program3) {
|
||||
const command = program3.command("test-server", { hidden: true });
|
||||
command.description("start test server");
|
||||
command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||
command.option("--host <host>", "Host to start the server on", "localhost");
|
||||
command.option("--port <port>", "Port to start the server on", "0");
|
||||
command.action(async (opts) => {
|
||||
const { runTestServerAction } = require("./testActions");
|
||||
await runTestServerAction(opts);
|
||||
});
|
||||
}
|
||||
function addShowReportCommand(program3) {
|
||||
const command = program3.command("show-report [report]");
|
||||
command.description("show HTML report");
|
||||
command.action(async (report, options) => {
|
||||
const { showReport } = require("./reportActions");
|
||||
await showReport(report, options.host, +options.port);
|
||||
});
|
||||
command.option("--host <host>", "Host to serve report on", "localhost");
|
||||
command.option("--port <port>", "Port to serve report on", "9323");
|
||||
command.addHelpText("afterAll", `
|
||||
Arguments [report]:
|
||||
When specified, opens given report, otherwise opens last generated report.
|
||||
|
||||
Examples:
|
||||
$ npx playwright show-report
|
||||
$ npx playwright show-report playwright-report`);
|
||||
}
|
||||
function addMergeReportsCommand(program3) {
|
||||
const command = program3.command("merge-reports [dir]");
|
||||
command.description("merge multiple blob reports (for sharded tests) into a single report");
|
||||
command.action(async (dir, options) => {
|
||||
try {
|
||||
const { mergeReports } = require("./reportActions");
|
||||
await mergeReports(dir, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
|
||||
}
|
||||
});
|
||||
command.option("-c, --config <file>", `Configuration file. Can be used to specify additional configuration for the output report.`);
|
||||
command.option("--reporter <reporter>", `Reporter to use, comma-separated, can be ${import_config.builtInReporters.map((name) => `"${name}"`).join(", ")} (default: "${import_config.defaultReporter}")`);
|
||||
command.addHelpText("afterAll", `
|
||||
Arguments [dir]:
|
||||
Directory containing blob reports.
|
||||
|
||||
Examples:
|
||||
$ npx playwright merge-reports playwright-report`);
|
||||
}
|
||||
function addTestMCPServerCommand(program3) {
|
||||
const command = program3.command("run-test-mcp-server", { hidden: true });
|
||||
command.description("Interact with the test runner over MCP");
|
||||
command.option("--headless", "run browser in headless mode, headed by default");
|
||||
command.option("-c, --config <file>", `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`);
|
||||
command.option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.");
|
||||
command.option("--port <port>", "port to listen on for SSE transport.");
|
||||
command.action(async (options) => {
|
||||
const { start, setupExitWatchdog } = await import("playwright-core/lib/tools/exports");
|
||||
const { TestServerBackend, testServerBackendTools } = require("./mcp/test/testBackend");
|
||||
setupExitWatchdog();
|
||||
const factory = {
|
||||
name: "Playwright Test Runner",
|
||||
nameInConfig: "playwright-test-runner",
|
||||
version: packageJSON.version,
|
||||
toolSchemas: testServerBackendTools.map((tool) => tool.schema),
|
||||
create: async () => new TestServerBackend(options.config, { muteConsole: options.port === void 0, headless: options.headless }),
|
||||
disposed: async () => {
|
||||
}
|
||||
};
|
||||
await start(factory, { port: options.port === void 0 ? void 0 : +options.port, host: options.host });
|
||||
});
|
||||
}
|
||||
function addInitAgentsCommand(program3) {
|
||||
const command = program3.command("init-agents");
|
||||
command.description("Initialize repository agents");
|
||||
const option = command.createOption("--loop <loop>", "Agentic loop provider");
|
||||
option.choices(["claude", "copilot", "opencode", "vscode", "vscode-legacy"]);
|
||||
command.addOption(option);
|
||||
command.option("-c, --config <file>", `Configuration file to find a project to use for seed test`);
|
||||
command.option("--project <project>", "Project to use for seed test");
|
||||
command.option("--prompts", "Whether to include prompts in the agent initialization");
|
||||
command.action(async (opts) => {
|
||||
const { loadConfigFromFile } = require("./common/configLoader");
|
||||
const { ClaudeGenerator, OpencodeGenerator, VSCodeGenerator, CopilotGenerator } = require("./agents/generateAgents");
|
||||
const config = await loadConfigFromFile(opts.config);
|
||||
if (opts.loop === "opencode") {
|
||||
await OpencodeGenerator.init(config, opts.project, opts.prompts);
|
||||
} else if (opts.loop === "vscode-legacy") {
|
||||
await VSCodeGenerator.init(config, opts.project);
|
||||
} else if (opts.loop === "claude") {
|
||||
await ClaudeGenerator.init(config, opts.project, opts.prompts);
|
||||
} else {
|
||||
await CopilotGenerator.init(config, opts.project, opts.prompts);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
const kTraceModes = ["on", "off", "on-first-retry", "on-all-retries", "retain-on-failure", "retain-on-first-failure", "retain-on-failure-and-retries"];
|
||||
const testOptions = [
|
||||
/* deprecated */
|
||||
["--browser <browser>", { description: `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")` }],
|
||||
["-c, --config <file>", { description: `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"` }],
|
||||
["--debug [mode]", { description: `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`, choices: ["inspector", "cli"], preset: "inspector" }],
|
||||
["--fail-on-flaky-tests", { description: `Fail if any test is flagged as flaky (default: false)` }],
|
||||
["--forbid-only", { description: `Fail if test.only is called (default: false)` }],
|
||||
["--fully-parallel", { description: `Run all tests in parallel (default: false)` }],
|
||||
["--global-timeout <timeout>", { description: `Maximum time this test suite can run in milliseconds (default: unlimited)` }],
|
||||
["-g, --grep <grep>", { description: `Only run tests matching this regular expression (default: ".*")` }],
|
||||
["--grep-invert <grep>", { description: `Only run tests that do not match this regular expression` }],
|
||||
["--headed", { description: `Run tests in headed browsers (default: headless)` }],
|
||||
["--ignore-snapshots", { description: `Ignore screenshot and snapshot expectations` }],
|
||||
["--last-failed", { description: `Only re-run the failures` }],
|
||||
["--list", { description: `Collect all the tests and report them, but do not run` }],
|
||||
["--max-failures <N>", { description: `Stop after the first N failures` }],
|
||||
["--no-deps", { description: `Do not run project dependencies` }],
|
||||
["--output <dir>", { description: `Folder for output artifacts (default: "test-results")` }],
|
||||
["--only-changed [ref]", { description: `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.` }],
|
||||
["--pass-with-no-tests", { description: `Makes test run succeed even if no tests were found` }],
|
||||
["--project <project-name...>", { description: `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)` }],
|
||||
["--quiet", { description: `Suppress stdio` }],
|
||||
["--repeat-each <N>", { description: `Run each test N times (default: 1)` }],
|
||||
["--reporter <reporter>", { description: `Reporter to use, comma-separated, can be ${import_config.builtInReporters.map((name) => `"${name}"`).join(", ")} (default: "${import_config.defaultReporter}")` }],
|
||||
["--retries <retries>", { description: `Maximum retry count for flaky tests, zero for no retries (default: no retries)` }],
|
||||
["--run-agents <mode>", { description: `Run agents to generate the code for page.perform`, choices: ["missing", "all", "none"], preset: "none" }],
|
||||
["--shard <shard>", { description: `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"` }],
|
||||
["--test-list <file>", { description: `Path to a file containing a list of tests to run. See https://playwright.dev/docs/test-cli for more details.` }],
|
||||
["--test-list-invert <file>", { description: `Path to a file containing a list of tests to skip. See https://playwright.dev/docs/test-cli for more details.` }],
|
||||
["--timeout <timeout>", { description: `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${import_config.defaultTimeout})` }],
|
||||
["--trace <mode>", { description: `Force tracing mode`, choices: kTraceModes }],
|
||||
["--tsconfig <path>", { description: `Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately)` }],
|
||||
["--ui", { description: `Run tests in interactive UI mode` }],
|
||||
["--ui-host <host>", { description: `Host to serve UI on; specifying this option opens UI in a browser tab` }],
|
||||
["--ui-port <port>", { description: `Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab` }],
|
||||
["-u, --update-snapshots [mode]", { description: `Update snapshots with actual results. Running tests without the flag defaults to "missing"`, choices: ["all", "changed", "missing", "none"], preset: "changed" }],
|
||||
["--update-source-method <method>", { description: `Chooses the way source is updated (default: "patch")`, choices: ["overwrite", "3way", "patch"] }],
|
||||
["-j, --workers <workers>", { description: `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)` }],
|
||||
["-x", { description: `Stop after the first failure` }]
|
||||
];
|
||||
addTestCommand(import_program.program);
|
||||
addShowReportCommand(import_program.program);
|
||||
addMergeReportsCommand(import_program.program);
|
||||
addClearCacheCommand(import_program.program);
|
||||
addTestMCPServerCommand(import_program.program);
|
||||
addDevServerCommand(import_program.program);
|
||||
addTestServerCommand(import_program.program);
|
||||
addInitAgentsCommand(import_program.program);
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
program
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var reportActions_exports = {};
|
||||
__export(reportActions_exports, {
|
||||
mergeReports: () => mergeReports,
|
||||
showReport: () => showReport
|
||||
});
|
||||
module.exports = __toCommonJS(reportActions_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_config = require("./common/config");
|
||||
var import_configLoader = require("./common/configLoader");
|
||||
var import_html = require("./reporters/html");
|
||||
var import_merge = require("./reporters/merge");
|
||||
async function showReport(report, host, port) {
|
||||
await (0, import_html.showHTMLReport)(report, host, port);
|
||||
}
|
||||
async function mergeReports(reportDir, opts) {
|
||||
const configFile = opts.config;
|
||||
const config = configFile ? await (0, import_configLoader.loadConfigFromFile)(configFile) : await (0, import_configLoader.loadEmptyConfigForMergeReports)();
|
||||
const dir = import_path.default.resolve(process.cwd(), reportDir || "");
|
||||
const dirStat = await import_fs.default.promises.stat(dir).catch((e) => null);
|
||||
if (!dirStat)
|
||||
throw new Error("Directory does not exist: " + dir);
|
||||
if (!dirStat.isDirectory())
|
||||
throw new Error(`"${dir}" is not a directory`);
|
||||
let reporterDescriptions = resolveReporterOption(opts.reporter);
|
||||
if (!reporterDescriptions && configFile)
|
||||
reporterDescriptions = config.config.reporter;
|
||||
if (!reporterDescriptions)
|
||||
reporterDescriptions = [[import_config.defaultReporter]];
|
||||
const rootDirOverride = configFile ? config.config.rootDir : void 0;
|
||||
await (0, import_merge.createMergedReport)(config, dir, reporterDescriptions, rootDirOverride);
|
||||
(0, import_utils.gracefullyProcessExitDoNotHang)(0);
|
||||
}
|
||||
function resolveReporterOption(reporter) {
|
||||
if (!reporter || !reporter.length)
|
||||
return void 0;
|
||||
return reporter.split(",").map((r) => [resolveReporter(r)]);
|
||||
}
|
||||
function resolveReporter(id) {
|
||||
if (import_config.builtInReporters.includes(id))
|
||||
return id;
|
||||
const localPath = import_path.default.resolve(process.cwd(), id);
|
||||
if (import_fs.default.existsSync(localPath))
|
||||
return localPath;
|
||||
return require.resolve(id, { paths: [process.cwd()] });
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
mergeReports,
|
||||
showReport
|
||||
});
|
||||
+633
@@ -0,0 +1,633 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var base_exports = {};
|
||||
__export(base_exports, {
|
||||
TerminalReporter: () => TerminalReporter,
|
||||
fitToWidth: () => fitToWidth,
|
||||
formatError: () => formatError,
|
||||
formatFailure: () => formatFailure,
|
||||
formatResultFailure: () => formatResultFailure,
|
||||
formatRetry: () => formatRetry,
|
||||
internalScreen: () => internalScreen,
|
||||
kOutputSymbol: () => kOutputSymbol,
|
||||
markErrorsAsReported: () => markErrorsAsReported,
|
||||
nonTerminalScreen: () => nonTerminalScreen,
|
||||
prepareErrorStack: () => prepareErrorStack,
|
||||
relativeFilePath: () => relativeFilePath,
|
||||
resolveOutputFile: () => resolveOutputFile,
|
||||
separator: () => separator,
|
||||
stepSuffix: () => stepSuffix,
|
||||
terminalScreen: () => terminalScreen
|
||||
});
|
||||
module.exports = __toCommonJS(base_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_util = require("../util");
|
||||
var import_utilsBundle = require("../utilsBundle");
|
||||
const kOutputSymbol = Symbol("output");
|
||||
const DEFAULT_TTY_WIDTH = 100;
|
||||
const DEFAULT_TTY_HEIGHT = 40;
|
||||
const originalProcessStdout = process.stdout;
|
||||
const originalProcessStderr = process.stderr;
|
||||
const terminalScreen = (() => {
|
||||
let isTTY = !!originalProcessStdout.isTTY;
|
||||
let ttyWidth = originalProcessStdout.columns || 0;
|
||||
let ttyHeight = originalProcessStdout.rows || 0;
|
||||
if (process.env.PLAYWRIGHT_FORCE_TTY === "false" || process.env.PLAYWRIGHT_FORCE_TTY === "0") {
|
||||
isTTY = false;
|
||||
ttyWidth = 0;
|
||||
ttyHeight = 0;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY === "true" || process.env.PLAYWRIGHT_FORCE_TTY === "1") {
|
||||
isTTY = true;
|
||||
ttyWidth = originalProcessStdout.columns || DEFAULT_TTY_WIDTH;
|
||||
ttyHeight = originalProcessStdout.rows || DEFAULT_TTY_HEIGHT;
|
||||
} else if (process.env.PLAYWRIGHT_FORCE_TTY) {
|
||||
isTTY = true;
|
||||
const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/);
|
||||
if (sizeMatch) {
|
||||
ttyWidth = +sizeMatch[1];
|
||||
ttyHeight = +sizeMatch[2];
|
||||
} else {
|
||||
ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY;
|
||||
ttyHeight = DEFAULT_TTY_HEIGHT;
|
||||
}
|
||||
if (isNaN(ttyWidth))
|
||||
ttyWidth = DEFAULT_TTY_WIDTH;
|
||||
if (isNaN(ttyHeight))
|
||||
ttyHeight = DEFAULT_TTY_HEIGHT;
|
||||
}
|
||||
let useColors = isTTY;
|
||||
if (process.env.DEBUG_COLORS === "0" || process.env.DEBUG_COLORS === "false" || process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false")
|
||||
useColors = false;
|
||||
else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR)
|
||||
useColors = true;
|
||||
const colors = useColors ? import_utils2.colors : import_utils2.noColors;
|
||||
return {
|
||||
resolveFiles: "cwd",
|
||||
isTTY,
|
||||
ttyWidth,
|
||||
ttyHeight,
|
||||
colors,
|
||||
stdout: originalProcessStdout,
|
||||
stderr: originalProcessStderr
|
||||
};
|
||||
})();
|
||||
const nonTerminalScreen = {
|
||||
colors: terminalScreen.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
ttyHeight: 0,
|
||||
resolveFiles: "rootDir"
|
||||
};
|
||||
const internalScreen = {
|
||||
colors: import_utils2.colors,
|
||||
isTTY: false,
|
||||
ttyWidth: 0,
|
||||
ttyHeight: 0,
|
||||
resolveFiles: "rootDir"
|
||||
};
|
||||
class TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
this.totalTestCount = 0;
|
||||
this.fileDurations = /* @__PURE__ */ new Map();
|
||||
this._fatalErrors = [];
|
||||
this._failureCount = 0;
|
||||
this.screen = options.screen ?? terminalScreen;
|
||||
this._options = options;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
this.totalTestCount = suite.allTests().length;
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._appendOutput({ chunk, type: "stdout" }, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._appendOutput({ chunk, type: "stderr" }, result);
|
||||
}
|
||||
_appendOutput(output, result) {
|
||||
if (!result)
|
||||
return;
|
||||
result[kOutputSymbol] = result[kOutputSymbol] || [];
|
||||
result[kOutputSymbol].push(output);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
if (result.status !== "skipped" && result.status !== test.expectedStatus)
|
||||
++this._failureCount;
|
||||
const projectName = test.titlePath()[1];
|
||||
const relativePath = relativeTestPath(this.screen, this.config, test);
|
||||
const fileAndProject = (projectName ? `[${projectName}] \u203A ` : "") + relativePath;
|
||||
const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: /* @__PURE__ */ new Set() };
|
||||
entry.duration += result.duration;
|
||||
entry.workers.add(result.workerIndex);
|
||||
this.fileDurations.set(fileAndProject, entry);
|
||||
}
|
||||
onError(error) {
|
||||
this._fatalErrors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
this.result = result;
|
||||
}
|
||||
fitToScreen(line, prefix) {
|
||||
if (!this.screen.ttyWidth) {
|
||||
return line;
|
||||
}
|
||||
return fitToWidth(line, this.screen.ttyWidth, prefix);
|
||||
}
|
||||
generateStartingMessage() {
|
||||
const jobs = this.config.metadata.actualWorkers ?? this.config.workers;
|
||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : "";
|
||||
if (!this.totalTestCount)
|
||||
return "";
|
||||
return "\n" + this.screen.colors.dim("Running ") + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? "s" : ""} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? "s" : ""}${shardDetails}`);
|
||||
}
|
||||
getSlowTests() {
|
||||
if (!this.config.reportSlowTests)
|
||||
return [];
|
||||
const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]);
|
||||
fileDurations.sort((a, b) => b[1] - a[1]);
|
||||
const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY);
|
||||
const threshold = this.config.reportSlowTests.threshold;
|
||||
return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count);
|
||||
}
|
||||
generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }) {
|
||||
const tokens = [];
|
||||
if (unexpected.length) {
|
||||
tokens.push(this.screen.colors.red(` ${unexpected.length} failed`));
|
||||
for (const test of unexpected)
|
||||
tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (interrupted.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`));
|
||||
for (const test of interrupted)
|
||||
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (flaky.length) {
|
||||
tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`));
|
||||
for (const test of flaky)
|
||||
tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
}
|
||||
if (skipped)
|
||||
tokens.push(this.screen.colors.yellow(` ${skipped} skipped`));
|
||||
if (didNotRun)
|
||||
tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`));
|
||||
if (expected)
|
||||
tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${(0, import_utils.msToString)(this.result.duration)})`));
|
||||
if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0)
|
||||
tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? "1 error was not a part of any test" : fatalErrors.length + " errors were not a part of any test"}, see above for details`));
|
||||
return tokens.join("\n");
|
||||
}
|
||||
generateSummary() {
|
||||
let didNotRun = 0;
|
||||
let skipped = 0;
|
||||
let expected = 0;
|
||||
const interrupted = [];
|
||||
const interruptedToPrint = [];
|
||||
const unexpected = [];
|
||||
const flaky = [];
|
||||
this.suite.allTests().forEach((test) => {
|
||||
switch (test.outcome()) {
|
||||
case "skipped": {
|
||||
if (test.results.some((result) => result.status === "interrupted")) {
|
||||
if (test.results.some((result) => !!result.error))
|
||||
interruptedToPrint.push(test);
|
||||
interrupted.push(test);
|
||||
} else if (!test.results.length || test.expectedStatus !== "skipped") {
|
||||
++didNotRun;
|
||||
} else {
|
||||
++skipped;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "expected":
|
||||
++expected;
|
||||
break;
|
||||
case "unexpected":
|
||||
unexpected.push(test);
|
||||
break;
|
||||
case "flaky":
|
||||
flaky.push(test);
|
||||
break;
|
||||
}
|
||||
});
|
||||
const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint];
|
||||
return {
|
||||
didNotRun,
|
||||
skipped,
|
||||
expected,
|
||||
interrupted,
|
||||
unexpected,
|
||||
flaky,
|
||||
failuresToPrint,
|
||||
fatalErrors: this._fatalErrors
|
||||
};
|
||||
}
|
||||
epilogue(full) {
|
||||
const summary = this.generateSummary();
|
||||
const summaryMessage = this.generateSummaryMessage(summary);
|
||||
if (full && summary.failuresToPrint.length && !this._options.omitFailures)
|
||||
this._printFailures(summary.failuresToPrint);
|
||||
this._printSlowTests();
|
||||
this._printSummary(summaryMessage);
|
||||
}
|
||||
_printFailures(failures) {
|
||||
this.writeLine("");
|
||||
failures.forEach((test, index) => {
|
||||
this.writeLine(this.formatFailure(test, index + 1));
|
||||
});
|
||||
}
|
||||
_printSlowTests() {
|
||||
const slowTests = this.getSlowTests();
|
||||
slowTests.forEach(([file, duration]) => {
|
||||
this.writeLine(this.screen.colors.yellow(" Slow test file: ") + file + this.screen.colors.yellow(` (${(0, import_utils.msToString)(duration)})`));
|
||||
});
|
||||
if (slowTests.length)
|
||||
this.writeLine(this.screen.colors.yellow(" Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel"));
|
||||
}
|
||||
_printSummary(summary) {
|
||||
if (summary.trim())
|
||||
this.writeLine(summary);
|
||||
}
|
||||
willRetry(test) {
|
||||
return test.outcome() === "unexpected" && test.results.length <= test.retries;
|
||||
}
|
||||
formatTestTitle(test, step) {
|
||||
return formatTestTitle(this.screen, this.config, test, step, this._options);
|
||||
}
|
||||
formatTestHeader(test, options = {}) {
|
||||
return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId });
|
||||
}
|
||||
formatFailure(test, index) {
|
||||
return formatFailure(this.screen, this.config, test, index, this._options);
|
||||
}
|
||||
formatError(error) {
|
||||
return formatError(this.screen, error);
|
||||
}
|
||||
formatResultErrors(test, result) {
|
||||
return formatResultErrors(this.screen, test, result);
|
||||
}
|
||||
writeLine(line) {
|
||||
this.screen.stdout?.write(line ? line + "\n" : "\n");
|
||||
}
|
||||
}
|
||||
function formatResultErrors(screen, test, result) {
|
||||
const lines = [];
|
||||
if (test.outcome() === "unexpected") {
|
||||
const errorDetails = formatResultFailure(screen, test, result, " ");
|
||||
if (errorDetails.length > 0)
|
||||
lines.push("");
|
||||
for (const error of errorDetails)
|
||||
lines.push(error.message, "");
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
function formatFailure(screen, config, test, index, options) {
|
||||
const lines = [];
|
||||
let printedHeader = false;
|
||||
for (const result of test.results) {
|
||||
const resultLines = [];
|
||||
const errors = formatResultFailure(screen, test, result, " ");
|
||||
if (!errors.length)
|
||||
continue;
|
||||
if (!printedHeader) {
|
||||
const header = formatTestHeader(screen, config, test, { indent: " ", index, mode: "error", includeTestId: options?.includeTestId });
|
||||
lines.push(screen.colors.red(header));
|
||||
printedHeader = true;
|
||||
}
|
||||
if (result.retry) {
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
resultLines.push(...errors.map((error) => "\n" + error.message));
|
||||
const attachmentGroups = groupAttachments(result.attachments);
|
||||
for (let i = 0; i < attachmentGroups.length; ++i) {
|
||||
const attachment = attachmentGroups[i];
|
||||
if (attachment.name === "error-context" && attachment.path) {
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config, attachment.path)}`));
|
||||
continue;
|
||||
}
|
||||
if (attachment.name.startsWith("_"))
|
||||
continue;
|
||||
const hasPrintableContent = attachment.contentType.startsWith("text/");
|
||||
if (!attachment.path && !hasPrintableContent)
|
||||
continue;
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`)));
|
||||
if (attachment.actual?.path) {
|
||||
if (attachment.expected?.path) {
|
||||
const expectedPath = relativeFilePath(screen, config, attachment.expected.path);
|
||||
resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`));
|
||||
}
|
||||
const actualPath = relativeFilePath(screen, config, attachment.actual.path);
|
||||
resultLines.push(screen.colors.dim(` Received: ${actualPath}`));
|
||||
if (attachment.previous?.path) {
|
||||
const previousPath = relativeFilePath(screen, config, attachment.previous.path);
|
||||
resultLines.push(screen.colors.dim(` Previous: ${previousPath}`));
|
||||
}
|
||||
if (attachment.diff?.path) {
|
||||
const diffPath = relativeFilePath(screen, config, attachment.diff.path);
|
||||
resultLines.push(screen.colors.dim(` Diff: ${diffPath}`));
|
||||
}
|
||||
} else if (attachment.path) {
|
||||
const relativePath = relativeFilePath(screen, config, attachment.path);
|
||||
resultLines.push(screen.colors.dim(` ${relativePath}`));
|
||||
if (attachment.name === "trace") {
|
||||
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
||||
resultLines.push(screen.colors.dim(` Usage:`));
|
||||
resultLines.push("");
|
||||
resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`));
|
||||
resultLines.push("");
|
||||
}
|
||||
} else {
|
||||
if (attachment.contentType.startsWith("text/") && attachment.body) {
|
||||
let text = attachment.body.toString();
|
||||
if (text.length > 300)
|
||||
text = text.slice(0, 300) + "...";
|
||||
for (const line of text.split("\n"))
|
||||
resultLines.push(screen.colors.dim(` ${line}`));
|
||||
}
|
||||
}
|
||||
resultLines.push(screen.colors.dim(separator(screen, " ")));
|
||||
}
|
||||
lines.push(...resultLines);
|
||||
}
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
function formatRetry(screen, result) {
|
||||
const retryLines = [];
|
||||
if (result.retry) {
|
||||
retryLines.push("");
|
||||
retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`)));
|
||||
}
|
||||
return retryLines;
|
||||
}
|
||||
function quotePathIfNeeded(path2) {
|
||||
if (/\s/.test(path2))
|
||||
return `"${path2}"`;
|
||||
return path2;
|
||||
}
|
||||
const kReportedSymbol = Symbol("reported");
|
||||
function markErrorsAsReported(result) {
|
||||
result[kReportedSymbol] = result.errors.length;
|
||||
}
|
||||
function formatResultFailure(screen, test, result, initialIndent) {
|
||||
const errorDetails = [];
|
||||
if (result.status === "passed" && test.expectedStatus === "failed") {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent)
|
||||
});
|
||||
}
|
||||
if (result.status === "interrupted") {
|
||||
errorDetails.push({
|
||||
message: indent(screen.colors.red(`Test was interrupted.`), initialIndent)
|
||||
});
|
||||
}
|
||||
const reportedIndex = result[kReportedSymbol] || 0;
|
||||
for (const error of result.errors.slice(reportedIndex)) {
|
||||
const formattedError = formatError(screen, error);
|
||||
errorDetails.push({
|
||||
message: indent(formattedError.message, initialIndent),
|
||||
location: formattedError.location
|
||||
});
|
||||
}
|
||||
return errorDetails;
|
||||
}
|
||||
function relativeFilePath(screen, config, file) {
|
||||
if (screen.resolveFiles === "cwd")
|
||||
return import_path.default.relative(process.cwd(), file);
|
||||
return import_path.default.relative(config.rootDir, file);
|
||||
}
|
||||
function relativeTestPath(screen, config, test) {
|
||||
return relativeFilePath(screen, config, test.location.file);
|
||||
}
|
||||
function stepSuffix(step) {
|
||||
const stepTitles = step ? step.titlePath() : [];
|
||||
return stepTitles.map((t) => t.split("\n")[0]).map((t) => " \u203A " + t).join("");
|
||||
}
|
||||
function formatTestTitle(screen, config, test, step, options = {}) {
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const location = `${relativeTestPath(screen, config, test)}:${test.location.line}:${test.location.column}`;
|
||||
const testId = options.includeTestId ? `[id=${test.id}] ` : "";
|
||||
const projectLabel = options.includeTestId ? `project=` : "";
|
||||
const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : "";
|
||||
const testTitle = `${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
|
||||
const extraTags = test.tags.filter((t) => !testTitle.includes(t) && !config.tags.includes(t));
|
||||
return `${testTitle}${stepSuffix(step)}${extraTags.length ? " " + extraTags.join(" ") : ""}`;
|
||||
}
|
||||
function formatTestHeader(screen, config, test, options = {}) {
|
||||
const title = formatTestTitle(screen, config, test, void 0, options);
|
||||
const header = `${options.indent || ""}${options.index ? options.index + ") " : ""}${title}`;
|
||||
let fullHeader = header;
|
||||
if (options.mode === "error") {
|
||||
const stepPaths = /* @__PURE__ */ new Set();
|
||||
for (const result of test.results.filter((r) => !!r.errors.length)) {
|
||||
const stepPath = [];
|
||||
const visit = (steps) => {
|
||||
const errors = steps.filter((s) => s.error);
|
||||
if (errors.length > 1)
|
||||
return;
|
||||
if (errors.length === 1 && errors[0].category === "test.step") {
|
||||
stepPath.push(errors[0].title);
|
||||
visit(errors[0].steps);
|
||||
}
|
||||
};
|
||||
visit(result.steps);
|
||||
stepPaths.add(["", ...stepPath].join(" \u203A "));
|
||||
}
|
||||
fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : "");
|
||||
}
|
||||
return separator(screen, fullHeader);
|
||||
}
|
||||
function formatError(screen, error) {
|
||||
const message = error.message || error.value || "";
|
||||
const stack = error.stack;
|
||||
if (!stack && !error.location)
|
||||
return { message };
|
||||
const tokens = [];
|
||||
const parsedStack = stack ? prepareErrorStack(stack) : void 0;
|
||||
tokens.push(parsedStack?.message || message);
|
||||
if (error.snippet) {
|
||||
let snippet = error.snippet;
|
||||
if (!screen.colors.enabled)
|
||||
snippet = (0, import_util.stripAnsiEscapes)(snippet);
|
||||
tokens.push("");
|
||||
tokens.push(snippet);
|
||||
}
|
||||
if (parsedStack && parsedStack.stackLines.length)
|
||||
tokens.push(screen.colors.dim(parsedStack.stackLines.join("\n")));
|
||||
let location = error.location;
|
||||
if (parsedStack && !location)
|
||||
location = parsedStack.location;
|
||||
if (error.cause)
|
||||
tokens.push(screen.colors.dim("[cause]: ") + formatError(screen, error.cause).message);
|
||||
return {
|
||||
location,
|
||||
message: tokens.join("\n")
|
||||
};
|
||||
}
|
||||
function separator(screen, text = "") {
|
||||
if (text)
|
||||
text += " ";
|
||||
const columns = Math.min(100, screen.ttyWidth || 100);
|
||||
return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length)));
|
||||
}
|
||||
function indent(lines, tab) {
|
||||
return lines.replace(/^(?=.+$)/gm, tab);
|
||||
}
|
||||
function prepareErrorStack(stack) {
|
||||
return (0, import_utils.parseErrorStack)(stack, import_path.default.sep, !!process.env.PWDEBUGIMPL);
|
||||
}
|
||||
function characterWidth(c) {
|
||||
return import_utilsBundle.getEastAsianWidth.eastAsianWidth(c.codePointAt(0));
|
||||
}
|
||||
function stringWidth(v) {
|
||||
let width = 0;
|
||||
for (const { segment } of new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v))
|
||||
width += characterWidth(segment);
|
||||
return width;
|
||||
}
|
||||
function suffixOfWidth(v, width) {
|
||||
const segments = [...new Intl.Segmenter(void 0, { granularity: "grapheme" }).segment(v)];
|
||||
let suffixBegin = v.length;
|
||||
for (const { segment, index } of segments.reverse()) {
|
||||
const segmentWidth = stringWidth(segment);
|
||||
if (segmentWidth > width)
|
||||
break;
|
||||
width -= segmentWidth;
|
||||
suffixBegin = index;
|
||||
}
|
||||
return v.substring(suffixBegin);
|
||||
}
|
||||
function fitToWidth(line, width, prefix) {
|
||||
const prefixLength = prefix ? (0, import_util.stripAnsiEscapes)(prefix).length : 0;
|
||||
width -= prefixLength;
|
||||
if (stringWidth(line) <= width)
|
||||
return line;
|
||||
const parts = line.split(import_util.ansiRegex);
|
||||
const taken = [];
|
||||
for (let i = parts.length - 1; i >= 0; i--) {
|
||||
if (i % 2) {
|
||||
taken.push(parts[i]);
|
||||
} else {
|
||||
let part = suffixOfWidth(parts[i], width);
|
||||
const wasTruncated = part.length < parts[i].length;
|
||||
if (wasTruncated && parts[i].length > 0) {
|
||||
part = "\u2026" + suffixOfWidth(parts[i], width - 1);
|
||||
}
|
||||
taken.push(part);
|
||||
width -= stringWidth(part);
|
||||
}
|
||||
}
|
||||
return taken.reverse().join("");
|
||||
}
|
||||
function resolveFromEnv(name) {
|
||||
const value = process.env[name];
|
||||
if (value)
|
||||
return import_path.default.resolve(process.cwd(), value);
|
||||
return void 0;
|
||||
}
|
||||
function resolveOutputFile(reporterName, options) {
|
||||
const name = reporterName.toUpperCase();
|
||||
let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`);
|
||||
if (!outputFile && options.outputFile)
|
||||
outputFile = import_path.default.resolve(options.configDir, options.outputFile);
|
||||
if (outputFile)
|
||||
return { outputFile };
|
||||
let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`);
|
||||
if (!outputDir && options.outputDir)
|
||||
outputDir = import_path.default.resolve(options.configDir, options.outputDir);
|
||||
if (!outputDir && options.default)
|
||||
outputDir = (0, import_util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, void 0);
|
||||
if (!outputDir)
|
||||
outputDir = options.configDir;
|
||||
const reportName = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`] ?? options.fileName ?? options.default?.fileName;
|
||||
if (!reportName)
|
||||
return void 0;
|
||||
outputFile = import_path.default.resolve(outputDir, reportName);
|
||||
return { outputFile, outputDir };
|
||||
}
|
||||
function groupAttachments(attachments) {
|
||||
const result = [];
|
||||
const attachmentsByPrefix = /* @__PURE__ */ new Map();
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.path) {
|
||||
result.push(attachment);
|
||||
continue;
|
||||
}
|
||||
const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/);
|
||||
if (!match) {
|
||||
result.push(attachment);
|
||||
continue;
|
||||
}
|
||||
const [, name, category] = match;
|
||||
let group = attachmentsByPrefix.get(name);
|
||||
if (!group) {
|
||||
group = { ...attachment, name };
|
||||
attachmentsByPrefix.set(name, group);
|
||||
result.push(group);
|
||||
}
|
||||
if (category === "expected")
|
||||
group.expected = attachment;
|
||||
else if (category === "actual")
|
||||
group.actual = attachment;
|
||||
else if (category === "diff")
|
||||
group.diff = attachment;
|
||||
else if (category === "previous")
|
||||
group.previous = attachment;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TerminalReporter,
|
||||
fitToWidth,
|
||||
formatError,
|
||||
formatFailure,
|
||||
formatResultFailure,
|
||||
formatRetry,
|
||||
internalScreen,
|
||||
kOutputSymbol,
|
||||
markErrorsAsReported,
|
||||
nonTerminalScreen,
|
||||
prepareErrorStack,
|
||||
relativeFilePath,
|
||||
resolveOutputFile,
|
||||
separator,
|
||||
stepSuffix,
|
||||
terminalScreen
|
||||
});
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var blob_exports = {};
|
||||
__export(blob_exports, {
|
||||
BlobReporter: () => BlobReporter,
|
||||
currentBlobReportVersion: () => currentBlobReportVersion
|
||||
});
|
||||
module.exports = __toCommonJS(blob_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_stream = require("stream");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_base = require("./base");
|
||||
var import_teleEmitter = require("./teleEmitter");
|
||||
const currentBlobReportVersion = 2;
|
||||
class BlobReporter extends import_teleEmitter.TeleReporterEmitter {
|
||||
constructor(options) {
|
||||
super((message) => this._messages.push(message));
|
||||
this._messages = [];
|
||||
this._attachments = [];
|
||||
this._options = options;
|
||||
if (this._options.fileName && !this._options.fileName.endsWith(".zip"))
|
||||
throw new Error(`Blob report file name must end with .zip extension: ${this._options.fileName}`);
|
||||
this._salt = (0, import_utils2.createGuid)();
|
||||
}
|
||||
onConfigure(config) {
|
||||
const metadata = {
|
||||
version: currentBlobReportVersion,
|
||||
userAgent: (0, import_utils2.getUserAgent)(),
|
||||
// TODO: remove after some time, recommend config.tag instead.
|
||||
name: process.env.PWTEST_BOT_NAME,
|
||||
shard: config.shard ?? void 0,
|
||||
pathSeparator: import_path.default.sep
|
||||
};
|
||||
this._messages.push({
|
||||
method: "onBlobReportMetadata",
|
||||
params: metadata
|
||||
});
|
||||
this._config = config;
|
||||
super.onConfigure(config);
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
const zipFileName = await this._prepareOutputFile();
|
||||
const { yazl } = await import("playwright-core/lib/zipBundle");
|
||||
const zipFile = new yazl.ZipFile();
|
||||
const zipFinishPromise = new import_utils2.ManualPromise();
|
||||
const finishPromise = zipFinishPromise.catch((e) => {
|
||||
throw new Error(`Failed to write report ${zipFileName}: ` + e.message);
|
||||
});
|
||||
zipFile.on("error", (error) => zipFinishPromise.reject(error));
|
||||
zipFile.outputStream.pipe(import_fs.default.createWriteStream(zipFileName)).on("close", () => {
|
||||
zipFinishPromise.resolve(void 0);
|
||||
}).on("error", (error) => zipFinishPromise.reject(error));
|
||||
for (const { originalPath, zipEntryPath } of this._attachments) {
|
||||
if (!import_fs.default.statSync(originalPath, { throwIfNoEntry: false })?.isFile())
|
||||
continue;
|
||||
zipFile.addFile(originalPath, zipEntryPath);
|
||||
}
|
||||
const lines = this._messages.map((m) => JSON.stringify(m) + "\n");
|
||||
const content = import_stream.Readable.from(lines);
|
||||
zipFile.addReadStream(content, "report.jsonl");
|
||||
zipFile.end();
|
||||
await finishPromise;
|
||||
}
|
||||
async _prepareOutputFile() {
|
||||
const { outputFile, outputDir } = (0, import_base.resolveOutputFile)("BLOB", {
|
||||
...this._options,
|
||||
default: {
|
||||
fileName: this._defaultReportName(this._config),
|
||||
outputDir: "blob-report"
|
||||
}
|
||||
});
|
||||
if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE)
|
||||
await (0, import_utils.removeFolders)([outputDir]);
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(outputFile), { recursive: true });
|
||||
return outputFile;
|
||||
}
|
||||
_defaultReportName(config) {
|
||||
let reportName = "report";
|
||||
if (this._options._commandHash)
|
||||
reportName += "-" + (0, import_utils.sanitizeForFilePath)(this._options._commandHash);
|
||||
if (config.shard) {
|
||||
const paddedNumber = `${config.shard.current}`.padStart(`${config.shard.total}`.length, "0");
|
||||
reportName = `${reportName}-${paddedNumber}`;
|
||||
}
|
||||
return `${reportName}.zip`;
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
return super._serializeAttachments(attachments).map((attachment) => {
|
||||
if (!attachment.path)
|
||||
return attachment;
|
||||
const sha1 = (0, import_utils2.calculateSha1)(attachment.path + this._salt);
|
||||
const extension = import_utilsBundle.mime.getExtension(attachment.contentType) || "dat";
|
||||
const newPath = `resources/${sha1}.${extension}`;
|
||||
this._attachments.push({ originalPath: attachment.path, zipEntryPath: newPath });
|
||||
return {
|
||||
...attachment,
|
||||
path: newPath
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
BlobReporter,
|
||||
currentBlobReportVersion
|
||||
});
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var dot_exports = {};
|
||||
__export(dot_exports, {
|
||||
default: () => dot_default
|
||||
});
|
||||
module.exports = __toCommonJS(dot_exports);
|
||||
var import_base = require("./base");
|
||||
class DotReporter extends import_base.TerminalReporter {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._counter = 0;
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
this.writeLine(this.generateStartingMessage());
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
if (!this.config.quiet)
|
||||
this.screen.stdout.write(chunk);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
if (!this.config.quiet)
|
||||
this.screen.stderr.write(chunk);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
if (this._counter === 80) {
|
||||
this.screen.stdout.write("\n");
|
||||
this._counter = 0;
|
||||
}
|
||||
++this._counter;
|
||||
if (result.status === "skipped") {
|
||||
this.screen.stdout.write(this.screen.colors.yellow("\xB0"));
|
||||
return;
|
||||
}
|
||||
if (this.willRetry(test)) {
|
||||
this.screen.stdout.write(this.screen.colors.gray("\xD7"));
|
||||
return;
|
||||
}
|
||||
switch (test.outcome()) {
|
||||
case "expected":
|
||||
this.screen.stdout.write(this.screen.colors.green("\xB7"));
|
||||
break;
|
||||
case "unexpected":
|
||||
this.screen.stdout.write(this.screen.colors.red(result.status === "timedOut" ? "T" : "F"));
|
||||
break;
|
||||
case "flaky":
|
||||
this.screen.stdout.write(this.screen.colors.yellow("\xB1"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
this.writeLine("\n" + this.formatError(error).message);
|
||||
this._counter = 0;
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
return;
|
||||
this.screen.stdout.write("\n");
|
||||
if (test.outcome() === "unexpected") {
|
||||
this.writeLine(this.screen.colors.red(this.formatTestHeader(test, { indent: " " })));
|
||||
this.writeLine(this.formatResultErrors(test, result));
|
||||
(0, import_base.markErrorsAsReported)(result);
|
||||
this.writeLine(this.screen.colors.yellow(" Paused on error. Press Ctrl+C to end.") + "\n");
|
||||
} else {
|
||||
this.writeLine(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
this.writeLine(this.screen.colors.yellow(" Paused at test end. Press Ctrl+C to end.") + "\n");
|
||||
}
|
||||
this._counter = 0;
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
this.screen.stdout.write("\n");
|
||||
this.epilogue(true);
|
||||
}
|
||||
}
|
||||
var dot_default = DotReporter;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var empty_exports = {};
|
||||
__export(empty_exports, {
|
||||
default: () => empty_default
|
||||
});
|
||||
module.exports = __toCommonJS(empty_exports);
|
||||
class EmptyReporter {
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var empty_default = EmptyReporter;
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var github_exports = {};
|
||||
__export(github_exports, {
|
||||
GitHubReporter: () => GitHubReporter,
|
||||
default: () => github_default
|
||||
});
|
||||
module.exports = __toCommonJS(github_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_base = require("./base");
|
||||
var import_util = require("../util");
|
||||
class GitHubLogger {
|
||||
_log(message, type = "notice", options = {}) {
|
||||
message = message.replace(/\n/g, "%0A");
|
||||
const configs = Object.entries(options).map(([key, option]) => `${key}=${option}`).join(",");
|
||||
process.stdout.write((0, import_util.stripAnsiEscapes)(`::${type} ${configs}::${message}
|
||||
`));
|
||||
}
|
||||
debug(message, options) {
|
||||
this._log(message, "debug", options);
|
||||
}
|
||||
error(message, options) {
|
||||
this._log(message, "error", options);
|
||||
}
|
||||
notice(message, options) {
|
||||
this._log(message, "notice", options);
|
||||
}
|
||||
warning(message, options) {
|
||||
this._log(message, "warning", options);
|
||||
}
|
||||
}
|
||||
class GitHubReporter extends import_base.TerminalReporter {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
this.githubLogger = new GitHubLogger();
|
||||
this.screen = { ...this.screen, colors: import_utils.noColors };
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
this._printAnnotations();
|
||||
}
|
||||
onError(error) {
|
||||
const errorMessage = this.formatError(error).message;
|
||||
this.githubLogger.error(errorMessage);
|
||||
}
|
||||
_printAnnotations() {
|
||||
const summary = this.generateSummary();
|
||||
const summaryMessage = this.generateSummaryMessage(summary);
|
||||
if (summary.failuresToPrint.length)
|
||||
this._printFailureAnnotations(summary.failuresToPrint);
|
||||
this._printSlowTestAnnotations();
|
||||
this._printSummaryAnnotation(summaryMessage);
|
||||
}
|
||||
_printSlowTestAnnotations() {
|
||||
this.getSlowTests().forEach(([file, duration]) => {
|
||||
const filePath = workspaceRelativePath(import_path.default.join(process.cwd(), file));
|
||||
this.githubLogger.warning(`${filePath} took ${(0, import_utils.msToString)(duration)}`, {
|
||||
title: "Slow Test",
|
||||
file: filePath
|
||||
});
|
||||
});
|
||||
}
|
||||
_printSummaryAnnotation(summary) {
|
||||
this.githubLogger.notice(summary, {
|
||||
title: "\u{1F3AD} Playwright Run Summary"
|
||||
});
|
||||
}
|
||||
_printFailureAnnotations(failures) {
|
||||
failures.forEach((test, index) => {
|
||||
const title = this.formatTestTitle(test);
|
||||
const header = this.formatTestHeader(test, { indent: " ", index: index + 1, mode: "error" });
|
||||
for (const result of test.results) {
|
||||
const errors = (0, import_base.formatResultFailure)(this.screen, test, result, " ");
|
||||
for (const error of errors) {
|
||||
const options = {
|
||||
file: workspaceRelativePath(error.location?.file || test.location.file),
|
||||
title
|
||||
};
|
||||
if (error.location) {
|
||||
options.line = error.location.line;
|
||||
options.col = error.location.column;
|
||||
}
|
||||
const message = [header, ...(0, import_base.formatRetry)(this.screen, result), error.message].join("\n");
|
||||
this.githubLogger.error(message, options);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function workspaceRelativePath(filePath) {
|
||||
return import_path.default.relative(process.env["GITHUB_WORKSPACE"] ?? "", filePath);
|
||||
}
|
||||
var github_default = GitHubReporter;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
GitHubReporter
|
||||
});
|
||||
+666
@@ -0,0 +1,666 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var html_exports = {};
|
||||
__export(html_exports, {
|
||||
default: () => html_default,
|
||||
showHTMLReport: () => showHTMLReport,
|
||||
startHtmlReportServer: () => startHtmlReportServer
|
||||
});
|
||||
module.exports = __toCommonJS(html_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_stream = require("stream");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_utilsBundle2 = require("playwright-core/lib/utilsBundle");
|
||||
var import_base = require("./base");
|
||||
var import_babelBundle = require("../transform/babelBundle");
|
||||
var import_util = require("../util");
|
||||
const htmlReportOptions = ["always", "never", "on-failure"];
|
||||
const isHtmlReportOption = (type) => {
|
||||
return htmlReportOptions.includes(type);
|
||||
};
|
||||
class HtmlReporter {
|
||||
constructor(options) {
|
||||
this._topLevelErrors = [];
|
||||
this._reportConfigs = /* @__PURE__ */ new Map();
|
||||
this._machines = [];
|
||||
this._options = options;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
const { outputFolder, open: open2, attachmentsBaseURL, host, port } = this._resolveOptions();
|
||||
this._outputFolder = outputFolder;
|
||||
this._open = open2;
|
||||
this._host = host;
|
||||
this._port = port;
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
const reportedWarnings = /* @__PURE__ */ new Set();
|
||||
for (const project of this.config.projects) {
|
||||
if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) {
|
||||
const key = outputFolder + "|" + project.outputDir;
|
||||
if (reportedWarnings.has(key))
|
||||
continue;
|
||||
reportedWarnings.add(key);
|
||||
writeLine(import_utils2.colors.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`));
|
||||
writeLine(`
|
||||
html reporter folder: ${import_utils2.colors.bold(outputFolder)}
|
||||
test results folder: ${import_utils2.colors.bold(project.outputDir)}`);
|
||||
writeLine("");
|
||||
writeLine(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss.
|
||||
`);
|
||||
}
|
||||
}
|
||||
this.suite = suite;
|
||||
}
|
||||
_resolveOptions() {
|
||||
const outputFolder = reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", this._options.configDir, this._options.outputFolder);
|
||||
return {
|
||||
outputFolder,
|
||||
open: getHtmlReportOptionProcessEnv() || this._options.open || "on-failure",
|
||||
attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || "data/",
|
||||
host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host,
|
||||
port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port
|
||||
};
|
||||
}
|
||||
_isSubdirectory(parentDir, dir) {
|
||||
const relativePath = import_path.default.relative(parentDir, dir);
|
||||
return !!relativePath && !relativePath.startsWith("..") && !import_path.default.isAbsolute(relativePath);
|
||||
}
|
||||
onError(error) {
|
||||
this._topLevelErrors.push(error);
|
||||
}
|
||||
onReportConfigure(params) {
|
||||
this._reportConfigs.set(params.reportPath, params.config);
|
||||
}
|
||||
onReportEnd(params) {
|
||||
const config = this._reportConfigs.get(params.reportPath);
|
||||
if (config)
|
||||
this._machines.push({ config, result: params.result, reportPath: params.reportPath });
|
||||
}
|
||||
async onEnd(result) {
|
||||
const projectSuites = this.suite.suites;
|
||||
await (0, import_utils.removeFolders)([this._outputFolder]);
|
||||
const noSnippets = parseBooleanEnvVar("PLAYWRIGHT_HTML_NO_SNIPPETS") ?? this._options.noSnippets;
|
||||
const noCopyPrompt = parseBooleanEnvVar("PLAYWRIGHT_HTML_NO_COPY_PROMPT") ?? this._options.noCopyPrompt;
|
||||
const doNotInlineAssets = parseBooleanEnvVar("PLAYWRIGHT_HTML_DO_NOT_INLINE_ASSETS") ?? this._options.doNotInlineAssets ?? false;
|
||||
const { yazl } = await import("playwright-core/lib/zipBundle");
|
||||
const builder = new HtmlBuilder(yazl, this.config, this._outputFolder, this._attachmentsBaseURL, doNotInlineAssets, {
|
||||
title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title,
|
||||
noSnippets,
|
||||
noCopyPrompt
|
||||
});
|
||||
this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors, this._machines);
|
||||
}
|
||||
async onExit() {
|
||||
if (process.env.CI || !this._buildResult)
|
||||
return;
|
||||
const { ok, singleTestId } = this._buildResult;
|
||||
const isCodingAgent = !!process.env.CLAUDECODE || !!process.env.COPILOT_CLI;
|
||||
const shouldOpen = !isCodingAgent && !!process.stdin.isTTY && (this._open === "always" || !ok && this._open === "on-failure");
|
||||
if (shouldOpen) {
|
||||
await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId);
|
||||
} else if (this._options._mode === "test" && !!process.stdin.isTTY) {
|
||||
const packageManagerCommand = (0, import_utils.getPackageManagerExecCommand)();
|
||||
const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? "" : " " + import_path.default.relative(process.cwd(), this._outputFolder);
|
||||
const hostArg = this._host ? ` --host ${this._host}` : "";
|
||||
const portArg = this._port ? ` --port ${this._port}` : "";
|
||||
writeLine("");
|
||||
writeLine("To open last HTML report run:");
|
||||
writeLine(import_utils2.colors.cyan(`
|
||||
${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg}
|
||||
`));
|
||||
}
|
||||
}
|
||||
}
|
||||
function reportFolderFromEnv() {
|
||||
const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT;
|
||||
return envValue ? import_path.default.resolve(envValue) : void 0;
|
||||
}
|
||||
function getHtmlReportOptionProcessEnv() {
|
||||
const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN;
|
||||
if (!htmlOpenEnv)
|
||||
return void 0;
|
||||
if (!isHtmlReportOption(htmlOpenEnv)) {
|
||||
writeLine(import_utils2.colors.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(", ")}`));
|
||||
return void 0;
|
||||
}
|
||||
return htmlOpenEnv;
|
||||
}
|
||||
function parseBooleanEnvVar(name) {
|
||||
const value = process.env[name];
|
||||
if (value === "false" || value === "0")
|
||||
return false;
|
||||
if (value)
|
||||
return true;
|
||||
return void 0;
|
||||
}
|
||||
function standaloneDefaultFolder() {
|
||||
return reportFolderFromEnv() ?? (0, import_util.resolveReporterOutputPath)("playwright-report", process.cwd(), void 0);
|
||||
}
|
||||
async function showHTMLReport(reportFolder, host = "localhost", port, testId) {
|
||||
const folder = reportFolder ?? standaloneDefaultFolder();
|
||||
try {
|
||||
(0, import_utils.assert)(import_fs.default.statSync(folder).isDirectory());
|
||||
} catch (e) {
|
||||
writeLine(import_utils2.colors.red(`No report found at "${folder}"`));
|
||||
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
|
||||
return;
|
||||
}
|
||||
const server = startHtmlReportServer(folder);
|
||||
await server.start({ port, host, preferredPort: port ? void 0 : 9323 });
|
||||
let url = server.urlPrefix("human-readable");
|
||||
writeLine("");
|
||||
writeLine(import_utils2.colors.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`));
|
||||
if (testId)
|
||||
url += `#?testId=${testId}`;
|
||||
url = url.replace("0.0.0.0", "localhost");
|
||||
await (0, import_utilsBundle.open)(url, { wait: true }).catch(() => {
|
||||
});
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
function startHtmlReportServer(folder) {
|
||||
const server = new import_utils.HttpServer();
|
||||
server.routePrefix("/", (request, response) => {
|
||||
let relativePath = new URL("http://localhost" + request.url).pathname;
|
||||
if (relativePath.startsWith("/trace/file")) {
|
||||
const url = new URL("http://localhost" + request.url);
|
||||
try {
|
||||
return server.serveFile(request, response, url.searchParams.get("path"));
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (relativePath === "/")
|
||||
relativePath = "/index.html";
|
||||
const absolutePath = import_path.default.join(folder, ...relativePath.split("/"));
|
||||
return server.serveFile(request, response, absolutePath);
|
||||
});
|
||||
return server;
|
||||
}
|
||||
class HtmlBuilder {
|
||||
constructor(yazl, config, outputDir, attachmentsBaseURL, doNotInlineAssets, options) {
|
||||
this._stepsInFile = new import_utils.MultiMap();
|
||||
this._hasTraces = false;
|
||||
this._dataZipFile = new yazl.ZipFile();
|
||||
this._config = config;
|
||||
this._reportFolder = outputDir;
|
||||
this._options = options;
|
||||
this._doNotInlineAssets = doNotInlineAssets;
|
||||
import_fs.default.mkdirSync(this._reportFolder, { recursive: true });
|
||||
this._attachmentsBaseURL = attachmentsBaseURL;
|
||||
}
|
||||
async build(metadata, projectSuites, result, topLevelErrors, machines) {
|
||||
const data = /* @__PURE__ */ new Map();
|
||||
for (const projectSuite of projectSuites) {
|
||||
const projectName = projectSuite.project().name;
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
const fileName = this._relativeLocation(fileSuite.location).file;
|
||||
this._createEntryForSuite(data, projectName, fileSuite, fileName, true);
|
||||
}
|
||||
}
|
||||
if (!this._options.noSnippets)
|
||||
createSnippets(this._stepsInFile);
|
||||
let ok = true;
|
||||
for (const [fileId, { testFile, testFileSummary }] of data) {
|
||||
const stats = testFileSummary.stats;
|
||||
for (const test of testFileSummary.tests) {
|
||||
if (test.outcome === "expected")
|
||||
++stats.expected;
|
||||
if (test.outcome === "skipped")
|
||||
++stats.skipped;
|
||||
if (test.outcome === "unexpected")
|
||||
++stats.unexpected;
|
||||
if (test.outcome === "flaky")
|
||||
++stats.flaky;
|
||||
++stats.total;
|
||||
}
|
||||
stats.ok = stats.unexpected + stats.flaky === 0;
|
||||
if (!stats.ok)
|
||||
ok = false;
|
||||
const testCaseSummaryComparator = (t1, t2) => {
|
||||
const w1 = (t1.outcome === "unexpected" ? 1e3 : 0) + (t1.outcome === "flaky" ? 1 : 0);
|
||||
const w2 = (t2.outcome === "unexpected" ? 1e3 : 0) + (t2.outcome === "flaky" ? 1 : 0);
|
||||
return w2 - w1;
|
||||
};
|
||||
testFileSummary.tests.sort(testCaseSummaryComparator);
|
||||
this._addDataFile(fileId + ".json", testFile);
|
||||
}
|
||||
const htmlReport = {
|
||||
metadata,
|
||||
startTime: result.startTime.getTime(),
|
||||
duration: result.duration,
|
||||
files: [...data.values()].map((e) => e.testFileSummary),
|
||||
projectNames: projectSuites.map((r) => r.project().name),
|
||||
stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) },
|
||||
errors: topLevelErrors.map((error) => (0, import_base.formatError)(import_base.internalScreen, error).message),
|
||||
options: this._options,
|
||||
machines: machines.map((machine) => ({
|
||||
duration: machine.result.duration,
|
||||
startTime: machine.result.startTime.getTime(),
|
||||
tag: machine.config.tags,
|
||||
shardIndex: machine.config.shard?.current
|
||||
}))
|
||||
};
|
||||
htmlReport.files.sort((f1, f2) => {
|
||||
const w1 = f1.stats.unexpected * 1e3 + f1.stats.flaky;
|
||||
const w2 = f2.stats.unexpected * 1e3 + f2.stats.flaky;
|
||||
return w2 - w1;
|
||||
});
|
||||
this._addDataFile("report.json", htmlReport);
|
||||
let singleTestId;
|
||||
if (htmlReport.stats.total === 1) {
|
||||
const testFile = data.values().next().value.testFile;
|
||||
singleTestId = testFile.tests[0].testId;
|
||||
}
|
||||
const reportIndexFile = await this._writeStaticAssets();
|
||||
if (this._hasTraces) {
|
||||
const traceViewerFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "traceViewer");
|
||||
const traceViewerTargetFolder = import_path.default.join(this._reportFolder, "trace");
|
||||
const traceViewerAssetsTargetFolder = import_path.default.join(traceViewerTargetFolder, "assets");
|
||||
import_fs.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true });
|
||||
for (const file of import_fs.default.readdirSync(traceViewerFolder)) {
|
||||
if (file.endsWith(".map") || file.includes("watch") || file.includes("assets"))
|
||||
continue;
|
||||
await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, file), import_path.default.join(traceViewerTargetFolder, file));
|
||||
}
|
||||
for (const file of import_fs.default.readdirSync(import_path.default.join(traceViewerFolder, "assets"))) {
|
||||
if (file.endsWith(".map") || file.includes("xtermModule"))
|
||||
continue;
|
||||
await (0, import_utils.copyFileAndMakeWritable)(import_path.default.join(traceViewerFolder, "assets", file), import_path.default.join(traceViewerAssetsTargetFolder, file));
|
||||
}
|
||||
}
|
||||
await this._writeReportData(reportIndexFile);
|
||||
return { ok, singleTestId };
|
||||
}
|
||||
async _writeStaticAssets() {
|
||||
const appFolder = import_path.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "htmlReport");
|
||||
const reportIndexFile = import_path.default.join(this._reportFolder, "index.html");
|
||||
if (this._doNotInlineAssets) {
|
||||
const html = await import_fs.default.promises.readFile(import_path.default.join(appFolder, "index.html"), "utf-8");
|
||||
await Promise.all([
|
||||
import_fs.default.promises.writeFile(reportIndexFile, html),
|
||||
import_fs.default.promises.copyFile(import_path.default.join(appFolder, "report.js"), import_path.default.join(this._reportFolder, "report.js")),
|
||||
import_fs.default.promises.copyFile(import_path.default.join(appFolder, "report.css"), import_path.default.join(this._reportFolder, "report.css"))
|
||||
]);
|
||||
} else {
|
||||
let html = await import_fs.default.promises.readFile(import_path.default.join(appFolder, "index.html"), "utf-8");
|
||||
const [js, css] = await Promise.all([
|
||||
import_fs.default.promises.readFile(import_path.default.join(appFolder, "report.js"), "utf-8"),
|
||||
import_fs.default.promises.readFile(import_path.default.join(appFolder, "report.css"), "utf-8")
|
||||
]);
|
||||
html = html.replace(/<script type="module"[^>]*><\/script>/, () => `<script type="module">${js}</script>`);
|
||||
html = html.replace(/<link rel="stylesheet"[^>]*>/, () => `<style type='text/css'>${css}</style>`);
|
||||
await import_fs.default.promises.writeFile(reportIndexFile, html);
|
||||
}
|
||||
return reportIndexFile;
|
||||
}
|
||||
async _writeReportData(filePath) {
|
||||
import_fs.default.appendFileSync(filePath, '<template id="playwrightReportBase64">data:application/zip;base64,');
|
||||
await new Promise((f) => {
|
||||
this._dataZipFile.end(void 0, () => {
|
||||
this._dataZipFile.outputStream.pipe(new Base64Encoder()).pipe(import_fs.default.createWriteStream(filePath, { flags: "a" })).on("close", f);
|
||||
});
|
||||
});
|
||||
import_fs.default.appendFileSync(filePath, "</template>");
|
||||
}
|
||||
_addDataFile(fileName, data) {
|
||||
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
|
||||
}
|
||||
_createEntryForSuite(data, projectName, suite, fileName, deep) {
|
||||
const fileId = (0, import_utils.calculateSha1)(fileName).slice(0, 20);
|
||||
let fileEntry = data.get(fileId);
|
||||
if (!fileEntry) {
|
||||
fileEntry = {
|
||||
testFile: { fileId, fileName, tests: [] },
|
||||
testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() }
|
||||
};
|
||||
data.set(fileId, fileEntry);
|
||||
}
|
||||
const { testFile, testFileSummary } = fileEntry;
|
||||
const testEntries = [];
|
||||
this._processSuite(suite, projectName, [], deep, testEntries);
|
||||
for (const test of testEntries) {
|
||||
testFile.tests.push(test.testCase);
|
||||
testFileSummary.tests.push(test.testCaseSummary);
|
||||
}
|
||||
}
|
||||
_processSuite(suite, projectName, path2, deep, outTests) {
|
||||
const newPath = [...path2, suite.title];
|
||||
suite.entries().forEach((e) => {
|
||||
if (e.type === "test")
|
||||
outTests.push(this._createTestEntry(e, projectName, newPath));
|
||||
else if (deep)
|
||||
this._processSuite(e, projectName, newPath, deep, outTests);
|
||||
});
|
||||
}
|
||||
_createTestEntry(test, projectName, path2) {
|
||||
const duration = test.results.reduce((a, r) => a + r.duration, 0);
|
||||
const location = this._relativeLocation(test.location);
|
||||
path2 = path2.slice(1).filter((path3) => path3.length > 0);
|
||||
const results = test.results.map((r) => this._createTestResult(test, r));
|
||||
return {
|
||||
testCase: {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
projectName,
|
||||
location,
|
||||
duration,
|
||||
annotations: this._serializeAnnotations(test.annotations),
|
||||
tags: test.tags,
|
||||
outcome: test.outcome(),
|
||||
path: path2,
|
||||
results,
|
||||
ok: test.outcome() === "expected" || test.outcome() === "flaky"
|
||||
},
|
||||
testCaseSummary: {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
projectName,
|
||||
location,
|
||||
duration,
|
||||
annotations: this._serializeAnnotations(test.annotations),
|
||||
tags: test.tags,
|
||||
outcome: test.outcome(),
|
||||
path: path2,
|
||||
ok: test.outcome() === "expected" || test.outcome() === "flaky",
|
||||
results: results.map((result) => {
|
||||
return {
|
||||
attachments: result.attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path })),
|
||||
startTime: result.startTime,
|
||||
workerIndex: result.workerIndex
|
||||
};
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
let lastAttachment;
|
||||
return attachments.map((a) => {
|
||||
if (a.name === "trace")
|
||||
this._hasTraces = true;
|
||||
if ((a.name === "stdout" || a.name === "stderr") && a.contentType === "text/plain") {
|
||||
if (lastAttachment && lastAttachment.name === a.name && lastAttachment.contentType === a.contentType) {
|
||||
lastAttachment.body += (0, import_util.stripAnsiEscapes)(a.body);
|
||||
return null;
|
||||
}
|
||||
a.body = (0, import_util.stripAnsiEscapes)(a.body);
|
||||
lastAttachment = a;
|
||||
return a;
|
||||
}
|
||||
if (a.path) {
|
||||
let fileName = a.path;
|
||||
try {
|
||||
const buffer = import_fs.default.readFileSync(a.path);
|
||||
const sha1 = (0, import_utils.calculateSha1)(buffer) + import_path.default.extname(a.path);
|
||||
fileName = this._attachmentsBaseURL + sha1;
|
||||
import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true });
|
||||
import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), buffer);
|
||||
} catch (e) {
|
||||
}
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: fileName,
|
||||
body: a.body
|
||||
};
|
||||
}
|
||||
if (a.body instanceof Buffer) {
|
||||
if (isTextContentType(a.contentType)) {
|
||||
const charset = a.contentType.match(/charset=(.*)/)?.[1];
|
||||
try {
|
||||
const body = a.body.toString(charset || "utf-8");
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
body
|
||||
};
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
import_fs.default.mkdirSync(import_path.default.join(this._reportFolder, "data"), { recursive: true });
|
||||
const extension = (0, import_utils.sanitizeForFilePath)(import_path.default.extname(a.name).replace(/^\./, "")) || import_utilsBundle2.mime.getExtension(a.contentType) || "dat";
|
||||
const sha1 = (0, import_utils.calculateSha1)(a.body) + "." + extension;
|
||||
import_fs.default.writeFileSync(import_path.default.join(this._reportFolder, "data", sha1), a.body);
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: this._attachmentsBaseURL + sha1
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
body: a.body
|
||||
};
|
||||
}).filter(Boolean);
|
||||
}
|
||||
_serializeAnnotations(annotations) {
|
||||
return annotations.map((a) => ({
|
||||
type: a.type,
|
||||
description: a.description === void 0 ? void 0 : String(a.description),
|
||||
location: a.location ? {
|
||||
file: a.location.file,
|
||||
line: a.location.line,
|
||||
column: a.location.column
|
||||
} : void 0
|
||||
}));
|
||||
}
|
||||
_createTestResult(test, result) {
|
||||
return {
|
||||
duration: result.duration,
|
||||
startTime: result.startTime.toISOString(),
|
||||
retry: result.retry,
|
||||
steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)),
|
||||
errors: (0, import_base.formatResultFailure)(import_base.internalScreen, test, result, "").map((error) => {
|
||||
return {
|
||||
message: error.message,
|
||||
codeframe: error.location ? createErrorCodeframe(error.message, error.location) : void 0
|
||||
};
|
||||
}),
|
||||
status: result.status,
|
||||
annotations: this._serializeAnnotations(result.annotations),
|
||||
attachments: this._serializeAttachments([
|
||||
...result.attachments,
|
||||
...result.stdout.map((m) => stdioAttachment(m, "stdout")),
|
||||
...result.stderr.map((m) => stdioAttachment(m, "stderr"))
|
||||
]),
|
||||
workerIndex: result.workerIndex
|
||||
};
|
||||
}
|
||||
_createTestStep(dedupedStep, result) {
|
||||
const { step, duration, count } = dedupedStep;
|
||||
const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip");
|
||||
let title = step.title;
|
||||
if (skipped)
|
||||
title = `${title} (skipped${skipped.description ? ": " + skipped.description : ""})`;
|
||||
const testStep = {
|
||||
title,
|
||||
startTime: step.startTime.toISOString(),
|
||||
duration,
|
||||
steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)),
|
||||
attachments: step.attachments.map((s) => {
|
||||
const index = result.attachments.indexOf(s);
|
||||
if (index === -1)
|
||||
throw new Error("Unexpected, attachment not found");
|
||||
return index;
|
||||
}),
|
||||
location: this._relativeLocation(step.location),
|
||||
error: step.error?.message,
|
||||
count,
|
||||
skipped: !!skipped
|
||||
};
|
||||
if (step.location)
|
||||
this._stepsInFile.set(step.location.file, testStep);
|
||||
return testStep;
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location)
|
||||
return void 0;
|
||||
const file = (0, import_utils.toPosixPath)(import_path.default.relative(this._config.rootDir, location.file));
|
||||
return {
|
||||
file,
|
||||
line: location.line,
|
||||
column: location.column
|
||||
};
|
||||
}
|
||||
}
|
||||
const emptyStats = () => {
|
||||
return {
|
||||
total: 0,
|
||||
expected: 0,
|
||||
unexpected: 0,
|
||||
flaky: 0,
|
||||
skipped: 0,
|
||||
ok: true
|
||||
};
|
||||
};
|
||||
const addStats = (stats, delta) => {
|
||||
stats.total += delta.total;
|
||||
stats.skipped += delta.skipped;
|
||||
stats.expected += delta.expected;
|
||||
stats.unexpected += delta.unexpected;
|
||||
stats.flaky += delta.flaky;
|
||||
stats.ok = stats.ok && delta.ok;
|
||||
return stats;
|
||||
};
|
||||
class Base64Encoder extends import_stream.Transform {
|
||||
_transform(chunk, encoding, callback) {
|
||||
if (this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = void 0;
|
||||
}
|
||||
const remaining = chunk.length % 3;
|
||||
if (remaining) {
|
||||
this._remainder = chunk.slice(chunk.length - remaining);
|
||||
chunk = chunk.slice(0, chunk.length - remaining);
|
||||
}
|
||||
chunk = chunk.toString("base64");
|
||||
this.push(Buffer.from(chunk));
|
||||
callback();
|
||||
}
|
||||
_flush(callback) {
|
||||
if (this._remainder)
|
||||
this.push(Buffer.from(this._remainder.toString("base64")));
|
||||
callback();
|
||||
}
|
||||
}
|
||||
function isTextContentType(contentType) {
|
||||
return contentType.startsWith("text/") || contentType.startsWith("application/json");
|
||||
}
|
||||
function stdioAttachment(chunk, type) {
|
||||
return {
|
||||
name: type,
|
||||
contentType: "text/plain",
|
||||
body: typeof chunk === "string" ? chunk : chunk.toString("utf-8")
|
||||
};
|
||||
}
|
||||
function dedupeSteps(steps) {
|
||||
const result = [];
|
||||
let lastResult = void 0;
|
||||
for (const step of steps) {
|
||||
const canDedupe = !step.error && step.duration >= 0 && step.location?.file && !step.steps.length;
|
||||
const lastStep = lastResult?.step;
|
||||
if (canDedupe && lastResult && lastStep && step.category === lastStep.category && step.title === lastStep.title && step.location?.file === lastStep.location?.file && step.location?.line === lastStep.location?.line && step.location?.column === lastStep.location?.column) {
|
||||
++lastResult.count;
|
||||
lastResult.duration += step.duration;
|
||||
continue;
|
||||
}
|
||||
lastResult = { step, count: 1, duration: step.duration };
|
||||
result.push(lastResult);
|
||||
if (!canDedupe)
|
||||
lastResult = void 0;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function createSnippets(stepsInFile) {
|
||||
for (const file of stepsInFile.keys()) {
|
||||
let source;
|
||||
try {
|
||||
source = import_fs.default.readFileSync(file, "utf-8") + "\n//";
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
const lines = source.split("\n").length;
|
||||
const highlighted = (0, import_babelBundle.codeFrameColumns)(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 });
|
||||
const highlightedLines = highlighted.split("\n");
|
||||
const lineWithArrow = highlightedLines[highlightedLines.length - 1];
|
||||
for (const step of stepsInFile.get(file)) {
|
||||
if (step.location.line < 2 || step.location.line >= lines)
|
||||
continue;
|
||||
const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1);
|
||||
const index = lineWithArrow.indexOf("^");
|
||||
const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index);
|
||||
snippetLines.splice(2, 0, shiftedArrow);
|
||||
step.snippet = snippetLines.join("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
function createErrorCodeframe(message, location) {
|
||||
let source;
|
||||
try {
|
||||
source = import_fs.default.readFileSync(location.file, "utf-8") + "\n//";
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
return (0, import_babelBundle.codeFrameColumns)(
|
||||
source,
|
||||
{
|
||||
start: {
|
||||
line: location.line,
|
||||
column: location.column
|
||||
}
|
||||
},
|
||||
{
|
||||
highlightCode: false,
|
||||
linesAbove: 100,
|
||||
linesBelow: 100,
|
||||
message: (0, import_util.stripAnsiEscapes)(message).split("\n")[0] || void 0
|
||||
}
|
||||
);
|
||||
}
|
||||
function writeLine(line) {
|
||||
process.stdout.write(line + "\n");
|
||||
}
|
||||
var html_default = HtmlReporter;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
showHTMLReport,
|
||||
startHtmlReportServer
|
||||
});
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var internalReporter_exports = {};
|
||||
__export(internalReporter_exports, {
|
||||
InternalReporter: () => InternalReporter,
|
||||
addLocationAndSnippetToError: () => addLocationAndSnippetToError
|
||||
});
|
||||
module.exports = __toCommonJS(internalReporter_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_base = require("./base");
|
||||
var import_multiplexer = require("./multiplexer");
|
||||
var import_test = require("../common/test");
|
||||
var import_babelBundle = require("../transform/babelBundle");
|
||||
var import_reporterV2 = require("./reporterV2");
|
||||
class InternalReporter {
|
||||
constructor(reporters) {
|
||||
this._didBegin = false;
|
||||
this._reporter = new import_multiplexer.Multiplexer(reporters.map(import_reporterV2.wrapReporterAsV2));
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this._config = config;
|
||||
this._startTime = /* @__PURE__ */ new Date();
|
||||
this._monotonicStartTime = (0, import_utils.monotonicTime)();
|
||||
this._reporter.onConfigure?.(config);
|
||||
}
|
||||
onBegin(suite) {
|
||||
this._didBegin = true;
|
||||
this._reporter.onBegin?.(suite);
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
this._reporter.onTestBegin?.(test, result);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._reporter.onStdOut?.(chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._reporter.onStdErr?.(chunk, test, result);
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
this._addSnippetToTestErrors(test, result);
|
||||
return await this._reporter.onTestPaused?.(test, result);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
this._addSnippetToTestErrors(test, result);
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
}
|
||||
async onEnd(result) {
|
||||
if (!this._didBegin) {
|
||||
this.onBegin(new import_test.Suite("", "root"));
|
||||
}
|
||||
return await this._reporter.onEnd?.({
|
||||
...result,
|
||||
startTime: this._startTime,
|
||||
duration: (0, import_utils.monotonicTime)() - this._monotonicStartTime
|
||||
});
|
||||
}
|
||||
async onExit() {
|
||||
await this._reporter.onExit?.();
|
||||
}
|
||||
onError(error) {
|
||||
addLocationAndSnippetToError(this._config, error);
|
||||
this._reporter.onError?.(error);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
this._reporter.onStepBegin?.(test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
this._addSnippetToStepError(test, step);
|
||||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
||||
}
|
||||
_addSnippetToTestErrors(test, result) {
|
||||
for (const error of result.errors)
|
||||
addLocationAndSnippetToError(this._config, error, test.location.file);
|
||||
}
|
||||
_addSnippetToStepError(test, step) {
|
||||
if (step.error)
|
||||
addLocationAndSnippetToError(this._config, step.error, test.location.file);
|
||||
}
|
||||
}
|
||||
function addLocationAndSnippetToError(config, error, file) {
|
||||
if (error.stack && !error.location)
|
||||
error.location = (0, import_base.prepareErrorStack)(error.stack).location;
|
||||
const location = error.location;
|
||||
if (!location)
|
||||
return;
|
||||
if (!!error.snippet)
|
||||
return;
|
||||
try {
|
||||
const tokens = [];
|
||||
const source = import_fs.default.readFileSync(location.file, "utf8");
|
||||
const codeFrame = (0, import_babelBundle.codeFrameColumns)(source, { start: location }, { highlightCode: true });
|
||||
if (!file || import_fs.default.realpathSync(file) !== location.file) {
|
||||
tokens.push(import_base.internalScreen.colors.gray(` at `) + `${(0, import_base.relativeFilePath)(import_base.internalScreen, config, location.file)}:${location.line}`);
|
||||
tokens.push("");
|
||||
}
|
||||
tokens.push(codeFrame);
|
||||
error.snippet = tokens.join("\n");
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
InternalReporter,
|
||||
addLocationAndSnippetToError
|
||||
});
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var json_exports = {};
|
||||
__export(json_exports, {
|
||||
default: () => json_default,
|
||||
serializePatterns: () => serializePatterns
|
||||
});
|
||||
module.exports = __toCommonJS(json_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_base = require("./base");
|
||||
var import_config = require("../common/config");
|
||||
class JSONReporter {
|
||||
constructor(options) {
|
||||
this._errors = [];
|
||||
this._resolvedOutputFile = (0, import_base.resolveOutputFile)("JSON", options)?.outputFile;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
printsToStdio() {
|
||||
return !this._resolvedOutputFile;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
}
|
||||
onError(error) {
|
||||
this._errors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
await outputReport(this._serializeReport(result), this._resolvedOutputFile);
|
||||
}
|
||||
_serializeReport(result) {
|
||||
const report = {
|
||||
config: {
|
||||
...removePrivateFields(this.config),
|
||||
rootDir: (0, import_utils.toPosixPath)(this.config.rootDir),
|
||||
projects: this.config.projects.map((project) => {
|
||||
return {
|
||||
outputDir: (0, import_utils.toPosixPath)(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
metadata: project.metadata,
|
||||
id: (0, import_config.getProjectId)(project),
|
||||
name: project.name,
|
||||
testDir: (0, import_utils.toPosixPath)(project.testDir),
|
||||
testIgnore: serializePatterns(project.testIgnore),
|
||||
testMatch: serializePatterns(project.testMatch),
|
||||
timeout: project.timeout
|
||||
};
|
||||
})
|
||||
},
|
||||
suites: this._mergeSuites(this.suite.suites),
|
||||
errors: this._errors,
|
||||
stats: {
|
||||
startTime: result.startTime.toISOString(),
|
||||
duration: result.duration,
|
||||
expected: 0,
|
||||
skipped: 0,
|
||||
unexpected: 0,
|
||||
flaky: 0
|
||||
}
|
||||
};
|
||||
for (const test of this.suite.allTests())
|
||||
++report.stats[test.outcome()];
|
||||
return report;
|
||||
}
|
||||
_mergeSuites(suites) {
|
||||
const fileSuites = new import_utils.MultiMap();
|
||||
for (const projectSuite of suites) {
|
||||
const projectId = (0, import_config.getProjectId)(projectSuite.project());
|
||||
const projectName = projectSuite.project().name;
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
const file = fileSuite.location.file;
|
||||
const serialized = this._serializeSuite(projectId, projectName, fileSuite);
|
||||
if (serialized)
|
||||
fileSuites.set(file, serialized);
|
||||
}
|
||||
}
|
||||
const results = [];
|
||||
for (const [, suites2] of fileSuites) {
|
||||
const result = {
|
||||
title: suites2[0].title,
|
||||
file: suites2[0].file,
|
||||
column: 0,
|
||||
line: 0,
|
||||
specs: []
|
||||
};
|
||||
for (const suite of suites2)
|
||||
this._mergeTestsFromSuite(result, suite);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location)
|
||||
return { file: "", line: 0, column: 0 };
|
||||
return {
|
||||
file: (0, import_utils.toPosixPath)(import_path.default.relative(this.config.rootDir, location.file)),
|
||||
line: location.line,
|
||||
column: location.column
|
||||
};
|
||||
}
|
||||
_locationMatches(s1, s2) {
|
||||
return s1.file === s2.file && s1.line === s2.line && s1.column === s2.column;
|
||||
}
|
||||
_mergeTestsFromSuite(to, from) {
|
||||
for (const fromSuite of from.suites || []) {
|
||||
const toSuite = (to.suites || []).find((s) => s.title === fromSuite.title && this._locationMatches(s, fromSuite));
|
||||
if (toSuite) {
|
||||
this._mergeTestsFromSuite(toSuite, fromSuite);
|
||||
} else {
|
||||
if (!to.suites)
|
||||
to.suites = [];
|
||||
to.suites.push(fromSuite);
|
||||
}
|
||||
}
|
||||
for (const spec of from.specs || []) {
|
||||
const toSpec = to.specs.find((s) => s.title === spec.title && s.file === (0, import_utils.toPosixPath)(import_path.default.relative(this.config.rootDir, spec.file)) && s.line === spec.line && s.column === spec.column);
|
||||
if (toSpec)
|
||||
toSpec.tests.push(...spec.tests);
|
||||
else
|
||||
to.specs.push(spec);
|
||||
}
|
||||
}
|
||||
_serializeSuite(projectId, projectName, suite) {
|
||||
if (!suite.allTests().length)
|
||||
return null;
|
||||
const suites = suite.suites.map((suite2) => this._serializeSuite(projectId, projectName, suite2)).filter((s) => s);
|
||||
return {
|
||||
title: suite.title,
|
||||
...this._relativeLocation(suite.location),
|
||||
specs: suite.tests.map((test) => this._serializeTestSpec(projectId, projectName, test)),
|
||||
suites: suites.length ? suites : void 0
|
||||
};
|
||||
}
|
||||
_serializeTestSpec(projectId, projectName, test) {
|
||||
return {
|
||||
title: test.title,
|
||||
ok: test.ok(),
|
||||
tags: test.tags.map((tag) => tag.substring(1)),
|
||||
// Strip '@'.
|
||||
tests: [this._serializeTest(projectId, projectName, test)],
|
||||
id: test.id,
|
||||
...this._relativeLocation(test.location)
|
||||
};
|
||||
}
|
||||
_serializeTest(projectId, projectName, test) {
|
||||
return {
|
||||
timeout: test.timeout,
|
||||
annotations: test.annotations,
|
||||
expectedStatus: test.expectedStatus,
|
||||
projectId,
|
||||
projectName,
|
||||
results: test.results.map((r) => this._serializeTestResult(r, test)),
|
||||
status: test.outcome()
|
||||
};
|
||||
}
|
||||
_serializeTestResult(result, test) {
|
||||
const steps = result.steps.filter((s) => s.category === "test.step");
|
||||
const jsonResult = {
|
||||
workerIndex: result.workerIndex,
|
||||
parallelIndex: result.parallelIndex,
|
||||
status: result.status,
|
||||
duration: result.duration,
|
||||
error: result.error,
|
||||
errors: result.errors.map((e) => this._serializeError(e)),
|
||||
stdout: result.stdout.map((s) => stdioEntry(s)),
|
||||
stderr: result.stderr.map((s) => stdioEntry(s)),
|
||||
retry: result.retry,
|
||||
steps: steps.length ? steps.map((s) => this._serializeTestStep(s)) : void 0,
|
||||
startTime: result.startTime.toISOString(),
|
||||
annotations: result.annotations,
|
||||
attachments: result.attachments.map((a) => ({
|
||||
name: a.name,
|
||||
contentType: a.contentType,
|
||||
path: a.path,
|
||||
body: a.body?.toString("base64")
|
||||
}))
|
||||
};
|
||||
if (result.error?.stack)
|
||||
jsonResult.errorLocation = (0, import_base.prepareErrorStack)(result.error.stack).location;
|
||||
return jsonResult;
|
||||
}
|
||||
_serializeError(error) {
|
||||
return (0, import_base.formatError)(import_base.nonTerminalScreen, error);
|
||||
}
|
||||
_serializeTestStep(step) {
|
||||
const steps = step.steps.filter((s) => s.category === "test.step");
|
||||
return {
|
||||
title: step.title,
|
||||
duration: step.duration,
|
||||
error: step.error,
|
||||
steps: steps.length ? steps.map((s) => this._serializeTestStep(s)) : void 0
|
||||
};
|
||||
}
|
||||
}
|
||||
async function outputReport(report, resolvedOutputFile) {
|
||||
const reportString = JSON.stringify(report, void 0, 2);
|
||||
if (resolvedOutputFile) {
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(resolvedOutputFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(resolvedOutputFile, reportString);
|
||||
} else {
|
||||
console.log(reportString);
|
||||
}
|
||||
}
|
||||
function stdioEntry(s) {
|
||||
if (typeof s === "string")
|
||||
return { text: s };
|
||||
return { buffer: s.toString("base64") };
|
||||
}
|
||||
function removePrivateFields(config) {
|
||||
return Object.fromEntries(Object.entries(config).filter(([name, value]) => !name.startsWith("_")));
|
||||
}
|
||||
function serializePatterns(patterns) {
|
||||
if (!Array.isArray(patterns))
|
||||
patterns = [patterns];
|
||||
return patterns.map((s) => s.toString());
|
||||
}
|
||||
var json_default = JSONReporter;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
serializePatterns
|
||||
});
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var junit_exports = {};
|
||||
__export(junit_exports, {
|
||||
default: () => junit_default
|
||||
});
|
||||
module.exports = __toCommonJS(junit_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_base = require("./base");
|
||||
var import_util = require("../util");
|
||||
class JUnitReporter {
|
||||
constructor(options) {
|
||||
this.totalTests = 0;
|
||||
this.totalFailures = 0;
|
||||
this.totalErrors = 0;
|
||||
this.totalSkipped = 0;
|
||||
this.stripANSIControlSequences = false;
|
||||
this.includeProjectInTestName = false;
|
||||
this.includeRetries = false;
|
||||
this.stripANSIControlSequences = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_JUNIT_STRIP_ANSI", !!options.stripANSIControlSequences);
|
||||
this.includeProjectInTestName = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME", !!options.includeProjectInTestName);
|
||||
this.includeRetries = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_JUNIT_INCLUDE_RETRIES", !!options.includeRetries);
|
||||
this.configDir = options.configDir;
|
||||
this.resolvedOutputFile = (0, import_base.resolveOutputFile)("JUNIT", options)?.outputFile;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
printsToStdio() {
|
||||
return !this.resolvedOutputFile;
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this.suite = suite;
|
||||
this.timestamp = /* @__PURE__ */ new Date();
|
||||
}
|
||||
async onEnd(result) {
|
||||
const children = [];
|
||||
for (const projectSuite of this.suite.suites) {
|
||||
for (const fileSuite of projectSuite.suites)
|
||||
children.push(await this._buildTestSuite(projectSuite.title, fileSuite));
|
||||
}
|
||||
const tokens = [];
|
||||
const self = this;
|
||||
const root = {
|
||||
name: "testsuites",
|
||||
attributes: {
|
||||
id: process.env[`PLAYWRIGHT_JUNIT_SUITE_ID`] || "",
|
||||
name: process.env[`PLAYWRIGHT_JUNIT_SUITE_NAME`] || "",
|
||||
tests: self.totalTests,
|
||||
failures: self.totalFailures,
|
||||
skipped: self.totalSkipped,
|
||||
errors: self.totalErrors,
|
||||
time: result.duration / 1e3
|
||||
},
|
||||
children
|
||||
};
|
||||
serializeXML(root, tokens, this.stripANSIControlSequences);
|
||||
const reportString = tokens.join("\n");
|
||||
if (this.resolvedOutputFile) {
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(this.resolvedOutputFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(this.resolvedOutputFile, reportString);
|
||||
} else {
|
||||
console.log(reportString);
|
||||
}
|
||||
}
|
||||
async _buildTestSuite(projectName, suite) {
|
||||
let tests = 0;
|
||||
let skipped = 0;
|
||||
let failures = 0;
|
||||
let errors = 0;
|
||||
let duration = 0;
|
||||
const children = [];
|
||||
const testCaseNamePrefix = projectName && this.includeProjectInTestName ? `[${projectName}] ` : "";
|
||||
for (const test of suite.allTests()) {
|
||||
++tests;
|
||||
if (test.outcome() === "skipped")
|
||||
++skipped;
|
||||
for (const result of test.results)
|
||||
duration += result.duration;
|
||||
const classification = await this._addTestCase(suite.title, testCaseNamePrefix, test, children);
|
||||
if (classification === "error")
|
||||
++errors;
|
||||
else if (classification === "failure")
|
||||
++failures;
|
||||
}
|
||||
this.totalTests += tests;
|
||||
this.totalSkipped += skipped;
|
||||
this.totalFailures += failures;
|
||||
this.totalErrors += errors;
|
||||
const entry = {
|
||||
name: "testsuite",
|
||||
attributes: {
|
||||
name: suite.title,
|
||||
timestamp: this.timestamp.toISOString(),
|
||||
hostname: projectName,
|
||||
tests,
|
||||
failures,
|
||||
skipped,
|
||||
time: duration / 1e3,
|
||||
errors
|
||||
},
|
||||
children
|
||||
};
|
||||
return entry;
|
||||
}
|
||||
async _addTestCase(suiteName, namePrefix, test, entries) {
|
||||
const entry = {
|
||||
name: "testcase",
|
||||
attributes: {
|
||||
// Skip root, project, file
|
||||
name: namePrefix + test.titlePath().slice(3).join(" \u203A "),
|
||||
// filename
|
||||
classname: suiteName
|
||||
},
|
||||
children: []
|
||||
};
|
||||
entries.push(entry);
|
||||
const properties = {
|
||||
name: "properties",
|
||||
children: []
|
||||
};
|
||||
for (const annotation of test.annotations) {
|
||||
const property = {
|
||||
name: "property",
|
||||
attributes: {
|
||||
name: annotation.type,
|
||||
value: annotation?.description ? annotation.description : ""
|
||||
}
|
||||
};
|
||||
properties.children?.push(property);
|
||||
}
|
||||
if (properties.children?.length)
|
||||
entry.children.push(properties);
|
||||
if (test.outcome() === "skipped") {
|
||||
entry.children.push({ name: "skipped" });
|
||||
return null;
|
||||
}
|
||||
if (this.includeRetries && test.ok()) {
|
||||
const passResult = test.results[test.results.length - 1];
|
||||
entry.attributes.time = passResult.duration / 1e3;
|
||||
await this._appendStdIO(entry, [passResult]);
|
||||
for (let i = 0; i < test.results.length - 1; i++) {
|
||||
const result = test.results[i];
|
||||
if (result.status === "passed" || result.status === "skipped")
|
||||
continue;
|
||||
entry.children.push(await this._buildRetryEntry(result, "flaky"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (this.includeRetries) {
|
||||
entry.attributes.time = test.results[0].duration / 1e3;
|
||||
await this._appendStdIO(entry, [test.results[0]]);
|
||||
for (let i = 1; i < test.results.length; i++) {
|
||||
const result = test.results[i];
|
||||
if (result.status === "passed" || result.status === "skipped")
|
||||
continue;
|
||||
entry.children.push(await this._buildRetryEntry(result, "rerun"));
|
||||
}
|
||||
return this._addFailureEntry(test, classifyResultError(test.results[0]), entry);
|
||||
}
|
||||
entry.attributes.time = test.results.reduce((acc, value) => acc + value.duration, 0) / 1e3;
|
||||
await this._appendStdIO(entry, test.results);
|
||||
if (test.ok())
|
||||
return null;
|
||||
return this._addFailureEntry(test, classifyTestError(test), entry);
|
||||
}
|
||||
_addFailureEntry(test, errorInfo, entry) {
|
||||
if (errorInfo) {
|
||||
entry.children.push({
|
||||
name: errorInfo.elementName,
|
||||
attributes: { message: errorInfo.message, type: errorInfo.type },
|
||||
text: (0, import_util.stripAnsiEscapes)((0, import_base.formatFailure)(import_base.nonTerminalScreen, this.config, test))
|
||||
});
|
||||
return errorInfo.elementName;
|
||||
}
|
||||
entry.children.push({
|
||||
name: "failure",
|
||||
attributes: {
|
||||
message: `${import_path.default.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`,
|
||||
type: "FAILURE"
|
||||
},
|
||||
text: (0, import_util.stripAnsiEscapes)((0, import_base.formatFailure)(import_base.nonTerminalScreen, this.config, test))
|
||||
});
|
||||
return "failure";
|
||||
}
|
||||
async _appendStdIO(entry, results) {
|
||||
const systemOut = [];
|
||||
const systemErr = [];
|
||||
for (const result of results) {
|
||||
for (const item of result.stdout)
|
||||
systemOut.push(item.toString());
|
||||
for (const item of result.stderr)
|
||||
systemErr.push(item.toString());
|
||||
for (const attachment of result.attachments) {
|
||||
if (!attachment.path)
|
||||
continue;
|
||||
let attachmentPath = import_path.default.relative(this.configDir, attachment.path);
|
||||
try {
|
||||
if (this.resolvedOutputFile)
|
||||
attachmentPath = import_path.default.relative(import_path.default.dirname(this.resolvedOutputFile), attachment.path);
|
||||
} catch {
|
||||
systemOut.push(`
|
||||
Warning: Unable to make attachment path ${attachment.path} relative to report output file ${this.resolvedOutputFile}`);
|
||||
}
|
||||
try {
|
||||
await import_fs.default.promises.access(attachment.path);
|
||||
systemOut.push(`
|
||||
[[ATTACHMENT|${attachmentPath}]]
|
||||
`);
|
||||
} catch {
|
||||
systemErr.push(`
|
||||
Warning: attachment ${attachmentPath} is missing`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (systemOut.length)
|
||||
entry.children.push({ name: "system-out", text: systemOut.join("") });
|
||||
if (systemErr.length)
|
||||
entry.children.push({ name: "system-err", text: systemErr.join("") });
|
||||
}
|
||||
async _buildRetryEntry(result, prefix) {
|
||||
const errorInfo = classifyResultError(result);
|
||||
const entry = {
|
||||
name: `${prefix}${errorInfo?.elementName === "error" ? "Error" : "Failure"}`,
|
||||
attributes: { message: errorInfo?.message || "", type: errorInfo?.type || "FAILURE", time: result.duration / 1e3 },
|
||||
children: []
|
||||
};
|
||||
const stackTrace = result.error?.stack || result.error?.message || result.error?.value || "";
|
||||
entry.children.push({ name: "stackTrace", text: (0, import_util.stripAnsiEscapes)(stackTrace) });
|
||||
await this._appendStdIO(entry, [result]);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
function classifyResultError(result) {
|
||||
const error = result.error;
|
||||
if (!error)
|
||||
return null;
|
||||
const rawMessage = (0, import_util.stripAnsiEscapes)(error.message || error.value || "");
|
||||
const nameMatch = rawMessage.match(/^(\w+): /);
|
||||
const errorName = nameMatch ? nameMatch[1] : "";
|
||||
const messageBody = nameMatch ? rawMessage.slice(nameMatch[0].length) : rawMessage;
|
||||
const firstLine = messageBody.split("\n")[0].trim();
|
||||
const matcherMatch = rawMessage.match(/expect\(.*?\)\.(not\.)?(\w+)/);
|
||||
if (matcherMatch) {
|
||||
const matcherName = `expect.${matcherMatch[1] || ""}${matcherMatch[2]}`;
|
||||
return {
|
||||
elementName: "failure",
|
||||
type: matcherName,
|
||||
message: firstLine
|
||||
};
|
||||
}
|
||||
return {
|
||||
elementName: "error",
|
||||
type: errorName || "Error",
|
||||
message: firstLine
|
||||
};
|
||||
}
|
||||
function classifyTestError(test) {
|
||||
for (const result of test.results) {
|
||||
const info = classifyResultError(result);
|
||||
if (info)
|
||||
return info;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function serializeXML(entry, tokens, stripANSIControlSequences) {
|
||||
const attrs = [];
|
||||
for (const [name, value] of Object.entries(entry.attributes || {}))
|
||||
attrs.push(`${name}="${escape(String(value), stripANSIControlSequences, false)}"`);
|
||||
tokens.push(`<${entry.name}${attrs.length ? " " : ""}${attrs.join(" ")}>`);
|
||||
for (const child of entry.children || [])
|
||||
serializeXML(child, tokens, stripANSIControlSequences);
|
||||
if (entry.text)
|
||||
tokens.push(escape(entry.text, stripANSIControlSequences, true));
|
||||
tokens.push(`</${entry.name}>`);
|
||||
}
|
||||
const discouragedXMLCharacters = /[\u0000-\u0008\u000b-\u000c\u000e-\u001f\u007f-\u0084\u0086-\u009f]/g;
|
||||
function escape(text, stripANSIControlSequences, isCharacterData) {
|
||||
if (stripANSIControlSequences)
|
||||
text = (0, import_util.stripAnsiEscapes)(text);
|
||||
if (isCharacterData) {
|
||||
text = "<![CDATA[" + text.replace(/]]>/g, "]]>") + "]]>";
|
||||
} else {
|
||||
const escapeRe = /[&"'<>]/g;
|
||||
text = text.replace(escapeRe, (c) => ({ "&": "&", '"': """, "'": "'", "<": "<", ">": ">" })[c]);
|
||||
}
|
||||
text = text.replace(discouragedXMLCharacters, "");
|
||||
return text;
|
||||
}
|
||||
var junit_default = JUnitReporter;
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var line_exports = {};
|
||||
__export(line_exports, {
|
||||
default: () => line_default
|
||||
});
|
||||
module.exports = __toCommonJS(line_exports);
|
||||
var import_base = require("./base");
|
||||
class LineReporter extends import_base.TerminalReporter {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this._current = 0;
|
||||
this._failures = 0;
|
||||
this._didBegin = false;
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
const startingMessage = this.generateStartingMessage();
|
||||
if (startingMessage) {
|
||||
this.writeLine(startingMessage);
|
||||
this.writeLine();
|
||||
}
|
||||
this._didBegin = true;
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, this.screen.stdout);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, this.screen.stderr);
|
||||
}
|
||||
_dumpToStdio(test, chunk, stream) {
|
||||
if (this.config.quiet)
|
||||
return;
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
stream.write(`\x1B[1A\x1B[2K`);
|
||||
if (test && this._lastTest !== test) {
|
||||
const title = this.screen.colors.dim(this.formatTestTitle(test));
|
||||
stream.write(this.fitToScreen(title) + `
|
||||
`);
|
||||
this._lastTest = test;
|
||||
}
|
||||
stream.write(chunk);
|
||||
if (chunk[chunk.length - 1] !== "\n")
|
||||
this.writeLine();
|
||||
this.writeLine();
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
++this._current;
|
||||
this._updateLine(test, result, void 0);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
if (this.screen.isTTY && step.category === "test.step")
|
||||
this._updateLine(test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
if (this.screen.isTTY && step.category === "test.step")
|
||||
this._updateLine(test, result, step.parent);
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
return;
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
this.screen.stdout.write(`\x1B[1A\x1B[2K`);
|
||||
if (test.outcome() === "unexpected") {
|
||||
this.writeLine(this.screen.colors.red(this.formatTestHeader(test, { indent: " ", index: ++this._failures })));
|
||||
this.writeLine(this.formatResultErrors(test, result));
|
||||
(0, import_base.markErrorsAsReported)(result);
|
||||
this.writeLine(this.screen.colors.yellow(` Paused on error. Press Ctrl+C to end.`) + "\n\n");
|
||||
} else {
|
||||
this.writeLine(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " })));
|
||||
this.writeLine(this.screen.colors.yellow(` Paused at test end. Press Ctrl+C to end.`) + "\n\n");
|
||||
}
|
||||
this._updateLine(test, result, void 0);
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
if (!this.willRetry(test) && (test.outcome() === "flaky" || test.outcome() === "unexpected" || result.status === "interrupted")) {
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
this.screen.stdout.write(`\x1B[1A\x1B[2K`);
|
||||
this.writeLine(this.formatFailure(test, ++this._failures));
|
||||
this.writeLine();
|
||||
}
|
||||
}
|
||||
_updateLine(test, result, step) {
|
||||
const retriesPrefix = result.retry ? ` (retries)` : ``;
|
||||
const prefix = `[${this._current}/${this.totalTestCount}]${retriesPrefix} `;
|
||||
const currentRetrySuffix = result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : "";
|
||||
const title = this.formatTestTitle(test, step) + currentRetrySuffix;
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
this.screen.stdout.write(`${prefix + title}
|
||||
`);
|
||||
else
|
||||
this.screen.stdout.write(`\x1B[1A\x1B[2K${prefix + this.fitToScreen(title, prefix)}
|
||||
`);
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
const message = this.formatError(error).message + "\n";
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin)
|
||||
this.screen.stdout.write(`\x1B[1A\x1B[2K`);
|
||||
this.screen.stdout.write(message);
|
||||
this.writeLine();
|
||||
}
|
||||
async onEnd(result) {
|
||||
if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin)
|
||||
this.screen.stdout.write(`\x1B[1A\x1B[2K`);
|
||||
await super.onEnd(result);
|
||||
this.epilogue(false);
|
||||
}
|
||||
}
|
||||
var line_default = LineReporter;
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var list_exports = {};
|
||||
__export(list_exports, {
|
||||
default: () => list_default
|
||||
});
|
||||
module.exports = __toCommonJS(list_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_base = require("./base");
|
||||
var import_util = require("../util");
|
||||
const DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === "win32" && process.env.TERM_PROGRAM !== "vscode" && !process.env.WT_SESSION;
|
||||
const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "ok" : "\u2713";
|
||||
const NEGATIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "x" : "\u2718";
|
||||
class ListReporter extends import_base.TerminalReporter {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._lastRow = 0;
|
||||
this._lastColumn = 0;
|
||||
this._testRows = /* @__PURE__ */ new Map();
|
||||
this._stepRows = /* @__PURE__ */ new Map();
|
||||
this._resultIndex = /* @__PURE__ */ new Map();
|
||||
this._stepIndex = /* @__PURE__ */ new Map();
|
||||
this._needNewLine = false;
|
||||
this._paused = /* @__PURE__ */ new Set();
|
||||
this._printSteps = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_LIST_PRINT_STEPS", options?.printSteps);
|
||||
}
|
||||
onBegin(suite) {
|
||||
super.onBegin(suite);
|
||||
const startingMessage = this.generateStartingMessage();
|
||||
if (startingMessage) {
|
||||
this.writeLine(startingMessage);
|
||||
this.writeLine("");
|
||||
}
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
const index = String(this._resultIndex.size + 1);
|
||||
this._resultIndex.set(result, index);
|
||||
if (!this.screen.isTTY)
|
||||
return;
|
||||
this._maybeWriteNewLine();
|
||||
this._testRows.set(test, this._lastRow);
|
||||
const prefix = this._testPrefix(index, "");
|
||||
const line = this.screen.colors.dim(this.formatTestTitle(test)) + this._retrySuffix(result);
|
||||
this._appendLine(line, prefix);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
super.onStdOut(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, this.screen.stdout, "out");
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
super.onStdErr(chunk, test, result);
|
||||
this._dumpToStdio(test, chunk, this.screen.stderr, "err");
|
||||
}
|
||||
getStepIndex(testIndex, result, step) {
|
||||
if (this._stepIndex.has(step))
|
||||
return this._stepIndex.get(step);
|
||||
const ordinal = (result[lastStepOrdinalSymbol] || 0) + 1;
|
||||
result[lastStepOrdinalSymbol] = ordinal;
|
||||
const stepIndex = `${testIndex}.${ordinal}`;
|
||||
this._stepIndex.set(step, stepIndex);
|
||||
return stepIndex;
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
if (step.category !== "test.step")
|
||||
return;
|
||||
const testIndex = this._resultIndex.get(result) || "";
|
||||
if (!this.screen.isTTY)
|
||||
return;
|
||||
if (this._printSteps) {
|
||||
this._maybeWriteNewLine();
|
||||
this._stepRows.set(step, this._lastRow);
|
||||
const prefix = this._testPrefix(this.getStepIndex(testIndex, result, step), "");
|
||||
const line = test.title + this.screen.colors.dim((0, import_base.stepSuffix)(step));
|
||||
this._appendLine(line, prefix);
|
||||
} else {
|
||||
this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, ""));
|
||||
}
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
if (step.category !== "test.step")
|
||||
return;
|
||||
const testIndex = this._resultIndex.get(result) || "";
|
||||
if (!this._printSteps) {
|
||||
if (this.screen.isTTY)
|
||||
this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, ""));
|
||||
return;
|
||||
}
|
||||
const index = this.getStepIndex(testIndex, result, step);
|
||||
const title = this.screen.isTTY ? test.title + this.screen.colors.dim((0, import_base.stepSuffix)(step)) : this.formatTestTitle(test, step);
|
||||
const prefix = this._testPrefix(index, "");
|
||||
let text = "";
|
||||
if (step.error)
|
||||
text = this.screen.colors.red(title);
|
||||
else
|
||||
text = title;
|
||||
text += this.screen.colors.dim(` (${(0, import_utils.msToString)(step.duration)})`);
|
||||
this._updateOrAppendLine(this._stepRows, step, text, prefix);
|
||||
}
|
||||
_maybeWriteNewLine() {
|
||||
if (this._needNewLine) {
|
||||
this._needNewLine = false;
|
||||
this.screen.stdout.write("\n");
|
||||
++this._lastRow;
|
||||
this._lastColumn = 0;
|
||||
}
|
||||
}
|
||||
_updateLineCountAndNewLineFlagForOutput(text) {
|
||||
this._needNewLine = text[text.length - 1] !== "\n";
|
||||
if (!this.screen.ttyWidth)
|
||||
return;
|
||||
for (const ch of text) {
|
||||
if (ch === "\n") {
|
||||
this._lastColumn = 0;
|
||||
++this._lastRow;
|
||||
continue;
|
||||
}
|
||||
++this._lastColumn;
|
||||
if (this._lastColumn > this.screen.ttyWidth) {
|
||||
this._lastColumn = 0;
|
||||
++this._lastRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
_dumpToStdio(test, chunk, stream, stdio) {
|
||||
if (this.config.quiet)
|
||||
return;
|
||||
const text = chunk.toString("utf-8");
|
||||
this._updateLineCountAndNewLineFlagForOutput(text);
|
||||
stream.write(chunk);
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
return;
|
||||
this._paused.add(result);
|
||||
this._updateTestLine(test, result);
|
||||
this._maybeWriteNewLine();
|
||||
if (test.outcome() === "unexpected") {
|
||||
const errors = this.formatResultErrors(test, result);
|
||||
this.writeLine(errors);
|
||||
this._updateLineCountAndNewLineFlagForOutput(errors);
|
||||
(0, import_base.markErrorsAsReported)(result);
|
||||
}
|
||||
this._appendLine(this.screen.colors.yellow(`Paused ${test.outcome() === "unexpected" ? "on error" : "at test end"}. Press Ctrl+C to end.`), this._testPrefix("", ""));
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
super.onTestEnd(test, result);
|
||||
const wasPaused = this._paused.delete(result);
|
||||
if (!wasPaused)
|
||||
this._updateTestLine(test, result);
|
||||
}
|
||||
_updateTestLine(test, result) {
|
||||
const title = this.formatTestTitle(test);
|
||||
let prefix = "";
|
||||
let text = "";
|
||||
let index = this._resultIndex.get(result);
|
||||
if (!index) {
|
||||
index = String(this._resultIndex.size + 1);
|
||||
this._resultIndex.set(result, index);
|
||||
}
|
||||
if (result.status === "skipped") {
|
||||
prefix = this._testPrefix(index, this.screen.colors.green("-"));
|
||||
text = this.screen.colors.cyan(title) + this._retrySuffix(result);
|
||||
} else {
|
||||
const statusMark = result.status === "passed" ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK;
|
||||
if (result.status === test.expectedStatus) {
|
||||
prefix = this._testPrefix(index, this.screen.colors.green(statusMark));
|
||||
text = title;
|
||||
} else {
|
||||
prefix = this._testPrefix(index, this.screen.colors.red(statusMark));
|
||||
text = this.screen.colors.red(title);
|
||||
}
|
||||
text += this._retrySuffix(result) + this.screen.colors.dim(` (${(0, import_utils.msToString)(result.duration)})`);
|
||||
}
|
||||
this._updateOrAppendLine(this._testRows, test, text, prefix);
|
||||
}
|
||||
_updateOrAppendLine(entityRowNumbers, entity, text, prefix) {
|
||||
const row = entityRowNumbers.get(entity);
|
||||
if (row !== void 0 && this.screen.isTTY && this._lastRow - row < this.screen.ttyHeight) {
|
||||
this._updateLine(row, text, prefix);
|
||||
} else {
|
||||
this._maybeWriteNewLine();
|
||||
entityRowNumbers.set(entity, this._lastRow);
|
||||
this._appendLine(text, prefix);
|
||||
}
|
||||
}
|
||||
_appendLine(text, prefix) {
|
||||
const line = prefix + this.fitToScreen(text, prefix);
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS) {
|
||||
this.screen.stdout.write("#" + this._lastRow + " : " + line + "\n");
|
||||
} else {
|
||||
this.screen.stdout.write(line);
|
||||
this.screen.stdout.write("\n");
|
||||
}
|
||||
++this._lastRow;
|
||||
this._lastColumn = 0;
|
||||
}
|
||||
_updateLine(row, text, prefix) {
|
||||
const line = prefix + this.fitToScreen(text, prefix);
|
||||
if (process.env.PW_TEST_DEBUG_REPORTERS)
|
||||
this.screen.stdout.write("#" + row + " : " + line + "\n");
|
||||
else
|
||||
this._updateLineForTTY(row, line);
|
||||
}
|
||||
_updateLineForTTY(row, line) {
|
||||
if (row !== this._lastRow)
|
||||
this.screen.stdout.write(`\x1B[${this._lastRow - row}A`);
|
||||
this.screen.stdout.write("\x1B[2K\x1B[0G");
|
||||
this.screen.stdout.write(line);
|
||||
if (row !== this._lastRow)
|
||||
this.screen.stdout.write(`\x1B[${this._lastRow - row}E`);
|
||||
}
|
||||
_testPrefix(index, statusMark) {
|
||||
const statusMarkLength = (0, import_util.stripAnsiEscapes)(statusMark).length;
|
||||
const indexLength = Math.ceil(Math.log10(this.totalTestCount + 1));
|
||||
return " " + statusMark + " ".repeat(3 - statusMarkLength) + this.screen.colors.dim(index.padStart(indexLength) + " ");
|
||||
}
|
||||
_retrySuffix(result) {
|
||||
return result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : "";
|
||||
}
|
||||
onError(error) {
|
||||
super.onError(error);
|
||||
this._maybeWriteNewLine();
|
||||
const message = this.formatError(error).message + "\n";
|
||||
this._updateLineCountAndNewLineFlagForOutput(message);
|
||||
this.screen.stdout.write(message);
|
||||
}
|
||||
async onEnd(result) {
|
||||
await super.onEnd(result);
|
||||
this.screen.stdout.write("\n");
|
||||
this.epilogue(true);
|
||||
}
|
||||
}
|
||||
const lastStepOrdinalSymbol = Symbol("lastStepOrdinal");
|
||||
var list_default = ListReporter;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var listModeReporter_exports = {};
|
||||
__export(listModeReporter_exports, {
|
||||
default: () => listModeReporter_default
|
||||
});
|
||||
module.exports = __toCommonJS(listModeReporter_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_base = require("./base");
|
||||
class ListModeReporter {
|
||||
constructor(options = {}) {
|
||||
this._options = options;
|
||||
this.screen = options?.screen ?? import_base.terminalScreen;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this.config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this._writeLine(`Listing tests:`);
|
||||
const tests = suite.allTests();
|
||||
const files = /* @__PURE__ */ new Set();
|
||||
for (const test of tests) {
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const location = `${import_path.default.relative(this.config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
|
||||
const testId = this._options.includeTestId ? `[id=${test.id}] ` : "";
|
||||
const projectLabel = this._options.includeTestId ? `project=` : "";
|
||||
const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : "";
|
||||
this._writeLine(` ${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`);
|
||||
files.add(test.location.file);
|
||||
}
|
||||
this._writeLine(`Total: ${tests.length} ${tests.length === 1 ? "test" : "tests"} in ${files.size} ${files.size === 1 ? "file" : "files"}`);
|
||||
}
|
||||
onError(error) {
|
||||
this.screen.stderr.write("\n" + (0, import_base.formatError)(import_base.terminalScreen, error).message + "\n");
|
||||
}
|
||||
_writeLine(line) {
|
||||
this.screen.stdout.write(line + "\n");
|
||||
}
|
||||
}
|
||||
var listModeReporter_default = ListModeReporter;
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var markdown_exports = {};
|
||||
__export(markdown_exports, {
|
||||
default: () => markdown_default
|
||||
});
|
||||
module.exports = __toCommonJS(markdown_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
class MarkdownReporter {
|
||||
constructor(options) {
|
||||
this._fatalErrors = [];
|
||||
this._options = options;
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
onBegin(config, suite) {
|
||||
this._config = config;
|
||||
this._suite = suite;
|
||||
}
|
||||
onError(error) {
|
||||
this._fatalErrors.push(error);
|
||||
}
|
||||
async onEnd(result) {
|
||||
const summary = this._generateSummary();
|
||||
const lines = [];
|
||||
if (this._fatalErrors.length)
|
||||
lines.push(`**${this._fatalErrors.length} fatal errors, not part of any test**`);
|
||||
if (summary.unexpected.length) {
|
||||
lines.push(`**${summary.unexpected.length} failed**`);
|
||||
this._printTestList(":x:", summary.unexpected, lines);
|
||||
}
|
||||
if (summary.flaky.length) {
|
||||
lines.push(`<details>`);
|
||||
lines.push(`<summary><b>${summary.flaky.length} flaky</b></summary>`);
|
||||
this._printTestList(":warning:", summary.flaky, lines, " <br/>");
|
||||
lines.push(`</details>`);
|
||||
lines.push(``);
|
||||
}
|
||||
if (summary.interrupted.length) {
|
||||
lines.push(`<details>`);
|
||||
lines.push(`<summary><b>${summary.interrupted.length} interrupted</b></summary>`);
|
||||
this._printTestList(":warning:", summary.interrupted, lines, " <br/>");
|
||||
lines.push(`</details>`);
|
||||
lines.push(``);
|
||||
}
|
||||
const skipped = summary.skipped ? `, ${summary.skipped} skipped` : "";
|
||||
const didNotRun = summary.didNotRun ? `, ${summary.didNotRun} did not run` : "";
|
||||
lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`);
|
||||
lines.push(``);
|
||||
await this.publishReport(lines.join("\n"));
|
||||
}
|
||||
async publishReport(report) {
|
||||
const maybeRelativeFile = this._options.outputFile || "report.md";
|
||||
const reportFile = import_path.default.resolve(this._options.configDir, maybeRelativeFile);
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(reportFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(reportFile, report);
|
||||
}
|
||||
_generateSummary() {
|
||||
let didNotRun = 0;
|
||||
let skipped = 0;
|
||||
let expected = 0;
|
||||
const interrupted = [];
|
||||
const interruptedToPrint = [];
|
||||
const unexpected = [];
|
||||
const flaky = [];
|
||||
this._suite.allTests().forEach((test) => {
|
||||
switch (test.outcome()) {
|
||||
case "skipped": {
|
||||
if (test.results.some((result) => result.status === "interrupted")) {
|
||||
if (test.results.some((result) => !!result.error))
|
||||
interruptedToPrint.push(test);
|
||||
interrupted.push(test);
|
||||
} else if (!test.results.length || test.expectedStatus !== "skipped") {
|
||||
++didNotRun;
|
||||
} else {
|
||||
++skipped;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "expected":
|
||||
++expected;
|
||||
break;
|
||||
case "unexpected":
|
||||
unexpected.push(test);
|
||||
break;
|
||||
case "flaky":
|
||||
flaky.push(test);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return {
|
||||
didNotRun,
|
||||
skipped,
|
||||
expected,
|
||||
interrupted,
|
||||
unexpected,
|
||||
flaky
|
||||
};
|
||||
}
|
||||
_printTestList(prefix, tests, lines, suffix) {
|
||||
for (const test of tests)
|
||||
lines.push(`${prefix} ${formatTestTitle(this._config.rootDir, test)}${suffix || ""}`);
|
||||
lines.push(``);
|
||||
}
|
||||
}
|
||||
function formatTestTitle(rootDir, test) {
|
||||
const [, projectName, , ...titles] = test.titlePath();
|
||||
const relativeTestPath = import_path.default.relative(rootDir, test.location.file);
|
||||
const location = `${relativeTestPath}:${test.location.line}`;
|
||||
const projectTitle = projectName ? `[${projectName}] \u203A ` : "";
|
||||
const testTitle = `${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
|
||||
const extraTags = test.tags.filter((t) => !testTitle.includes(t));
|
||||
const formattedTags = extraTags.map((t) => `\`${t}\``).join(" ");
|
||||
return `${testTitle}${extraTags.length ? " " + formattedTags : ""}`;
|
||||
}
|
||||
var markdown_default = MarkdownReporter;
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var merge_exports = {};
|
||||
__export(merge_exports, {
|
||||
createMergedReport: () => createMergedReport
|
||||
});
|
||||
module.exports = __toCommonJS(merge_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_blob = require("./blob");
|
||||
var import_multiplexer = require("./multiplexer");
|
||||
var import_stringInternPool = require("../isomorphic/stringInternPool");
|
||||
var import_teleReceiver = require("../isomorphic/teleReceiver");
|
||||
var import_reporters = require("../runner/reporters");
|
||||
var import_util = require("../util");
|
||||
async function createMergedReport(config, dir, reporterDescriptions, rootDirOverride) {
|
||||
const reporters = await (0, import_reporters.createReporters)(config, "merge", reporterDescriptions);
|
||||
const multiplexer = new import_multiplexer.Multiplexer(reporters);
|
||||
const stringPool = new import_stringInternPool.StringInternPool();
|
||||
let printStatus = () => {
|
||||
};
|
||||
if (!multiplexer.printsToStdio()) {
|
||||
printStatus = printStatusToStdout;
|
||||
printStatus(`merging reports from ${dir}`);
|
||||
}
|
||||
const shardFiles = await sortedShardFiles(dir);
|
||||
if (shardFiles.length === 0)
|
||||
throw new Error(`No report files found in ${dir}`);
|
||||
const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride);
|
||||
const pathSeparator = rootDirOverride ? import_path.default.sep : eventData.pathSeparatorFromMetadata ?? import_path.default.sep;
|
||||
const pathPackage = pathSeparator === "/" ? import_path.default.posix : import_path.default.win32;
|
||||
const receiver = new import_teleReceiver.TeleReporterReceiver(multiplexer, {
|
||||
mergeProjects: false,
|
||||
mergeTestCases: false,
|
||||
// When merging on a different OS, an absolute path like `C:\foo\bar` from win may look like
|
||||
// a relative path on posix, and vice versa.
|
||||
// Therefore, we cannot use `path.resolve()` here - it will resolve relative-looking paths
|
||||
// against `process.cwd()`, while we just want to normalize ".." and "." segments.
|
||||
resolvePath: (rootDir, relativePath) => stringPool.internString(pathPackage.normalize(pathPackage.join(rootDir, relativePath))),
|
||||
configOverrides: config.config
|
||||
});
|
||||
printStatus(`processing test events`);
|
||||
const dispatchEvents = async (events) => {
|
||||
for (const event of events) {
|
||||
if (event.method === "onEnd")
|
||||
printStatus(`building final report`);
|
||||
await receiver.dispatch(event);
|
||||
if (event.method === "onEnd")
|
||||
printStatus(`finished building report`);
|
||||
}
|
||||
};
|
||||
await dispatchEvents(eventData.prologue);
|
||||
let usedWorkers = 0;
|
||||
for (const { reportFile, zipFile, eventPatchers, metadata, config: config2, fullResult } of eventData.reports) {
|
||||
multiplexer.onReportConfigure({
|
||||
reportPath: zipFile,
|
||||
config: (0, import_teleReceiver.asFullConfig)(config2)
|
||||
});
|
||||
const reportJsonl = await import_fs.default.promises.readFile(reportFile);
|
||||
const events = parseTestEvents(reportJsonl);
|
||||
new import_stringInternPool.JsonStringInternalizer(stringPool).traverse(events);
|
||||
eventPatchers.patchers.push(new AttachmentPathPatcher(dir));
|
||||
if (metadata.name)
|
||||
eventPatchers.patchers.push(new GlobalErrorPatcher(metadata.name));
|
||||
if (config2?.tags?.length)
|
||||
eventPatchers.patchers.push(new GlobalErrorPatcher(config2.tags.join(" ")));
|
||||
const workerIndexPatcher = new WorkerIndexPatcher(usedWorkers);
|
||||
eventPatchers.patchers.push(workerIndexPatcher);
|
||||
eventPatchers.patchEvents(events);
|
||||
usedWorkers += workerIndexPatcher.usedWorkers();
|
||||
await dispatchEvents(events);
|
||||
multiplexer.onReportEnd({
|
||||
reportPath: zipFile,
|
||||
result: (0, import_teleReceiver.asFullResult)(fullResult)
|
||||
});
|
||||
}
|
||||
await dispatchEvents(eventData.epilogue);
|
||||
}
|
||||
const commonEventNames = ["onBlobReportMetadata", "onConfigure", "onProject", "onBegin", "onEnd"];
|
||||
const commonEvents = new Set(commonEventNames);
|
||||
const commonEventRegex = new RegExp(`${commonEventNames.join("|")}`);
|
||||
function parseCommonEvents(reportJsonl) {
|
||||
return splitBufferLines(reportJsonl).map((line) => line.toString("utf8")).filter((line) => commonEventRegex.test(line)).map((line) => JSON.parse(line)).filter((event) => commonEvents.has(event.method));
|
||||
}
|
||||
function parseTestEvents(reportJsonl) {
|
||||
return splitBufferLines(reportJsonl).map((line) => line.toString("utf8")).filter((line) => line.length).map((line) => JSON.parse(line)).filter((event) => !commonEvents.has(event.method));
|
||||
}
|
||||
function splitBufferLines(buffer) {
|
||||
const lines = [];
|
||||
let start = 0;
|
||||
while (start < buffer.length) {
|
||||
const end = buffer.indexOf(10, start);
|
||||
if (end === -1) {
|
||||
lines.push(buffer.slice(start));
|
||||
break;
|
||||
}
|
||||
lines.push(buffer.slice(start, end));
|
||||
start = end + 1;
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
async function extractAndParseReports(dir, shardFiles, internalizer, printStatus) {
|
||||
const shardEvents = [];
|
||||
await import_fs.default.promises.mkdir(import_path.default.join(dir, "resources"), { recursive: true });
|
||||
const reportNames = new UniqueFileNameGenerator();
|
||||
for (const file of shardFiles) {
|
||||
const absolutePath = import_path.default.join(dir, file);
|
||||
printStatus(`extracting: ${(0, import_util.relativeFilePath)(absolutePath)}`);
|
||||
const zipFile = new import_utils.ZipFile(absolutePath);
|
||||
const entryNames = await zipFile.entries();
|
||||
for (const entryName of entryNames.sort()) {
|
||||
let reportFile = import_path.default.join(dir, entryName);
|
||||
const content = await zipFile.read(entryName);
|
||||
if (entryName.endsWith(".jsonl")) {
|
||||
reportFile = reportNames.makeUnique(reportFile);
|
||||
let parsedEvents = parseCommonEvents(content);
|
||||
internalizer.traverse(parsedEvents);
|
||||
const metadata = findMetadata(parsedEvents, file);
|
||||
parsedEvents = modernizer.modernize(metadata.version, parsedEvents);
|
||||
shardEvents.push({
|
||||
zipFile: absolutePath,
|
||||
reportFile,
|
||||
metadata,
|
||||
parsedEvents
|
||||
});
|
||||
}
|
||||
await import_fs.default.promises.writeFile(reportFile, content);
|
||||
}
|
||||
zipFile.close();
|
||||
}
|
||||
return shardEvents;
|
||||
}
|
||||
function findMetadata(events, file) {
|
||||
if (events[0]?.method !== "onBlobReportMetadata")
|
||||
throw new Error(`No metadata event found in ${file}`);
|
||||
const metadata = events[0].params;
|
||||
if (metadata.version > import_blob.currentBlobReportVersion)
|
||||
throw new Error(`Blob report ${file} was created with a newer version of Playwright.`);
|
||||
return metadata;
|
||||
}
|
||||
async function mergeEvents(dir, shardReportFiles, stringPool, printStatus, rootDirOverride) {
|
||||
const internalizer = new import_stringInternPool.JsonStringInternalizer(stringPool);
|
||||
const configureEvents = [];
|
||||
const projectEvents = [];
|
||||
const endEvents = [];
|
||||
const blobs = await extractAndParseReports(dir, shardReportFiles, internalizer, printStatus);
|
||||
blobs.sort((a, b) => {
|
||||
const nameA = a.metadata.name ?? "";
|
||||
const nameB = b.metadata.name ?? "";
|
||||
if (nameA !== nameB)
|
||||
return nameA.localeCompare(nameB);
|
||||
const shardA = a.metadata.shard?.current ?? 0;
|
||||
const shardB = b.metadata.shard?.current ?? 0;
|
||||
if (shardA !== shardB)
|
||||
return shardA - shardB;
|
||||
return a.zipFile.localeCompare(b.zipFile);
|
||||
});
|
||||
printStatus(`merging events`);
|
||||
const reports = [];
|
||||
const globalTestIdSet = /* @__PURE__ */ new Set();
|
||||
for (let i = 0; i < blobs.length; ++i) {
|
||||
const { parsedEvents, metadata, reportFile, zipFile } = blobs[i];
|
||||
const eventPatchers = new JsonEventPatchers();
|
||||
eventPatchers.patchers.push(new IdsPatcher(
|
||||
stringPool,
|
||||
metadata.name,
|
||||
String(i),
|
||||
globalTestIdSet
|
||||
));
|
||||
if (rootDirOverride)
|
||||
eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator));
|
||||
eventPatchers.patchEvents(parsedEvents);
|
||||
let config;
|
||||
let fullResult;
|
||||
for (const event of parsedEvents) {
|
||||
if (event.method === "onConfigure") {
|
||||
configureEvents.push(event);
|
||||
config = event.params.config;
|
||||
} else if (event.method === "onProject") {
|
||||
projectEvents.push(event);
|
||||
} else if (event.method === "onEnd") {
|
||||
fullResult = event.params.result;
|
||||
endEvents.push({ event, metadata });
|
||||
}
|
||||
}
|
||||
reports.push({
|
||||
eventPatchers,
|
||||
reportFile,
|
||||
zipFile,
|
||||
metadata,
|
||||
config,
|
||||
fullResult
|
||||
});
|
||||
}
|
||||
return {
|
||||
prologue: [
|
||||
mergeConfigureEvents(configureEvents, rootDirOverride),
|
||||
...projectEvents,
|
||||
{ method: "onBegin", params: void 0 }
|
||||
],
|
||||
reports,
|
||||
epilogue: [
|
||||
mergeEndEvents(endEvents),
|
||||
{ method: "onExit", params: void 0 }
|
||||
],
|
||||
pathSeparatorFromMetadata: blobs[0]?.metadata.pathSeparator
|
||||
};
|
||||
}
|
||||
function mergeConfigureEvents(configureEvents, rootDirOverride) {
|
||||
if (!configureEvents.length)
|
||||
throw new Error("No configure events found");
|
||||
let config = {
|
||||
configFile: void 0,
|
||||
globalTimeout: 0,
|
||||
maxFailures: 0,
|
||||
metadata: {},
|
||||
shard: null,
|
||||
rootDir: "",
|
||||
version: "",
|
||||
workers: 0,
|
||||
globalSetup: null,
|
||||
globalTeardown: null
|
||||
};
|
||||
for (const event of configureEvents)
|
||||
config = mergeConfigs(config, event.params.config);
|
||||
if (rootDirOverride) {
|
||||
config.rootDir = rootDirOverride;
|
||||
} else {
|
||||
const rootDirs = new Set(configureEvents.map((e) => e.params.config.rootDir));
|
||||
if (rootDirs.size > 1) {
|
||||
throw new Error([
|
||||
`Blob reports being merged were recorded with different test directories, and`,
|
||||
`merging cannot proceed. This may happen if you are merging reports from`,
|
||||
`machines with different environments, like different operating systems or`,
|
||||
`if the tests ran with different playwright configs.`,
|
||||
``,
|
||||
`You can force merge by specifying a merge config file with "-c" option. If`,
|
||||
`you'd like all test paths to be correct, make sure 'testDir' in the merge config`,
|
||||
`file points to the actual tests location.`,
|
||||
``,
|
||||
`Found directories:`,
|
||||
...rootDirs
|
||||
].join("\n"));
|
||||
}
|
||||
}
|
||||
return {
|
||||
method: "onConfigure",
|
||||
params: {
|
||||
config
|
||||
}
|
||||
};
|
||||
}
|
||||
function mergeConfigs(to, from) {
|
||||
return {
|
||||
...to,
|
||||
...from,
|
||||
metadata: {
|
||||
...to.metadata,
|
||||
...from.metadata,
|
||||
actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0)
|
||||
},
|
||||
shard: null,
|
||||
workers: to.workers + from.workers
|
||||
};
|
||||
}
|
||||
function mergeEndEvents(endEvents) {
|
||||
let startTime = endEvents.length ? 1e13 : Date.now();
|
||||
let status = "passed";
|
||||
let endTime = 0;
|
||||
for (const { event } of endEvents) {
|
||||
const shardResult = event.params.result;
|
||||
if (shardResult.status === "failed")
|
||||
status = "failed";
|
||||
else if (shardResult.status === "timedout" && status !== "failed")
|
||||
status = "timedout";
|
||||
else if (shardResult.status === "interrupted" && status !== "failed" && status !== "timedout")
|
||||
status = "interrupted";
|
||||
startTime = Math.min(startTime, shardResult.startTime);
|
||||
endTime = Math.max(endTime, shardResult.startTime + shardResult.duration);
|
||||
}
|
||||
const result = {
|
||||
status,
|
||||
startTime,
|
||||
duration: endTime - startTime
|
||||
};
|
||||
return {
|
||||
method: "onEnd",
|
||||
params: {
|
||||
result
|
||||
}
|
||||
};
|
||||
}
|
||||
async function sortedShardFiles(dir) {
|
||||
const files = await import_fs.default.promises.readdir(dir);
|
||||
return files.filter((file) => file.endsWith(".zip")).sort();
|
||||
}
|
||||
function printStatusToStdout(message) {
|
||||
process.stdout.write(`${message}
|
||||
`);
|
||||
}
|
||||
class UniqueFileNameGenerator {
|
||||
constructor() {
|
||||
this._usedNames = /* @__PURE__ */ new Set();
|
||||
}
|
||||
makeUnique(name) {
|
||||
if (!this._usedNames.has(name)) {
|
||||
this._usedNames.add(name);
|
||||
return name;
|
||||
}
|
||||
const extension = import_path.default.extname(name);
|
||||
name = name.substring(0, name.length - extension.length);
|
||||
let index = 0;
|
||||
while (true) {
|
||||
const candidate = `${name}-${++index}${extension}`;
|
||||
if (!this._usedNames.has(candidate)) {
|
||||
this._usedNames.add(candidate);
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class IdsPatcher {
|
||||
constructor(stringPool, botName, salt, globalTestIdSet) {
|
||||
this._stringPool = stringPool;
|
||||
this._botName = botName;
|
||||
this._salt = salt;
|
||||
this._testIdsMap = /* @__PURE__ */ new Map();
|
||||
this._globalTestIdSet = globalTestIdSet;
|
||||
}
|
||||
patchEvent(event) {
|
||||
const { method, params } = event;
|
||||
switch (method) {
|
||||
case "onProject":
|
||||
this._onProject(params.project);
|
||||
return;
|
||||
case "onAttach":
|
||||
case "onTestBegin":
|
||||
case "onStepBegin":
|
||||
case "onStepEnd":
|
||||
case "onStdIO":
|
||||
params.testId = params.testId ? this._mapTestId(params.testId) : void 0;
|
||||
return;
|
||||
case "onTestEnd":
|
||||
params.test.testId = this._mapTestId(params.test.testId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_onProject(project) {
|
||||
project.metadata ??= {};
|
||||
project.suites.forEach((suite) => this._updateTestIds(suite));
|
||||
}
|
||||
_updateTestIds(suite) {
|
||||
suite.entries.forEach((entry) => {
|
||||
if ("testId" in entry)
|
||||
this._updateTestId(entry);
|
||||
else
|
||||
this._updateTestIds(entry);
|
||||
});
|
||||
}
|
||||
_updateTestId(test) {
|
||||
test.testId = this._mapTestId(test.testId);
|
||||
if (this._botName) {
|
||||
test.tags = test.tags || [];
|
||||
test.tags.unshift("@" + this._botName);
|
||||
}
|
||||
}
|
||||
_mapTestId(testId) {
|
||||
const t1 = this._stringPool.internString(testId);
|
||||
if (this._testIdsMap.has(t1))
|
||||
return this._testIdsMap.get(t1);
|
||||
if (this._globalTestIdSet.has(t1)) {
|
||||
const t2 = this._stringPool.internString(testId + this._salt);
|
||||
this._globalTestIdSet.add(t2);
|
||||
this._testIdsMap.set(t1, t2);
|
||||
return t2;
|
||||
}
|
||||
this._globalTestIdSet.add(t1);
|
||||
this._testIdsMap.set(t1, t1);
|
||||
return t1;
|
||||
}
|
||||
}
|
||||
class AttachmentPathPatcher {
|
||||
constructor(_resourceDir) {
|
||||
this._resourceDir = _resourceDir;
|
||||
}
|
||||
patchEvent(event) {
|
||||
if (event.method === "onAttach")
|
||||
this._patchAttachments(event.params.attachments);
|
||||
else if (event.method === "onTestEnd")
|
||||
this._patchAttachments(event.params.result.attachments ?? []);
|
||||
}
|
||||
_patchAttachments(attachments) {
|
||||
for (const attachment of attachments) {
|
||||
if (!attachment.path)
|
||||
continue;
|
||||
attachment.path = import_path.default.join(this._resourceDir, attachment.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
class PathSeparatorPatcher {
|
||||
constructor(from) {
|
||||
this._from = from ?? (import_path.default.sep === "/" ? "\\" : "/");
|
||||
this._to = import_path.default.sep;
|
||||
}
|
||||
patchEvent(jsonEvent) {
|
||||
if (this._from === this._to)
|
||||
return;
|
||||
if (jsonEvent.method === "onProject") {
|
||||
this._updateProject(jsonEvent.params.project);
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === "onTestEnd") {
|
||||
const test = jsonEvent.params.test;
|
||||
test.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
||||
const testResult = jsonEvent.params.result;
|
||||
testResult.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
||||
testResult.errors.forEach((error) => this._updateErrorLocations(error));
|
||||
(testResult.attachments ?? []).forEach((attachment) => {
|
||||
if (attachment.path)
|
||||
attachment.path = this._updatePath(attachment.path);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === "onStepBegin") {
|
||||
const step = jsonEvent.params.step;
|
||||
this._updateLocation(step.location);
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === "onStepEnd") {
|
||||
const step = jsonEvent.params.step;
|
||||
this._updateErrorLocations(step.error);
|
||||
step.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
||||
return;
|
||||
}
|
||||
if (jsonEvent.method === "onAttach") {
|
||||
const attach = jsonEvent.params;
|
||||
attach.attachments.forEach((attachment) => {
|
||||
if (attachment.path)
|
||||
attachment.path = this._updatePath(attachment.path);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
_updateProject(project) {
|
||||
project.outputDir = this._updatePath(project.outputDir);
|
||||
project.testDir = this._updatePath(project.testDir);
|
||||
project.snapshotDir = this._updatePath(project.snapshotDir);
|
||||
project.suites.forEach((suite) => this._updateSuite(suite, true));
|
||||
}
|
||||
_updateSuite(suite, isFileSuite = false) {
|
||||
this._updateLocation(suite.location);
|
||||
if (isFileSuite)
|
||||
suite.title = this._updatePath(suite.title);
|
||||
for (const entry of suite.entries) {
|
||||
if ("testId" in entry) {
|
||||
this._updateLocation(entry.location);
|
||||
entry.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation));
|
||||
} else {
|
||||
this._updateSuite(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
_updateErrorLocations(error) {
|
||||
while (error) {
|
||||
this._updateLocation(error.location);
|
||||
error = error.cause;
|
||||
}
|
||||
}
|
||||
_updateAnnotationLocation(annotation) {
|
||||
this._updateLocation(annotation.location);
|
||||
}
|
||||
_updateLocation(location) {
|
||||
if (location)
|
||||
location.file = this._updatePath(location.file);
|
||||
}
|
||||
_updatePath(text) {
|
||||
return text.split(this._from).join(this._to);
|
||||
}
|
||||
}
|
||||
class GlobalErrorPatcher {
|
||||
constructor(botName) {
|
||||
this._prefix = `(${botName}) `;
|
||||
}
|
||||
patchEvent(event) {
|
||||
if (event.method !== "onError")
|
||||
return;
|
||||
const error = event.params.error;
|
||||
if (error.message !== void 0)
|
||||
error.message = this._prefix + error.message;
|
||||
if (error.stack !== void 0)
|
||||
error.stack = this._prefix + error.stack;
|
||||
}
|
||||
}
|
||||
class WorkerIndexPatcher {
|
||||
constructor(baseWorkerIndex) {
|
||||
this._maxWorkerIndex = 0;
|
||||
this._baseWorkerIndex = baseWorkerIndex;
|
||||
}
|
||||
patchEvent(event) {
|
||||
if (event.method === "onTestBegin") {
|
||||
this._maxWorkerIndex = Math.max(this._maxWorkerIndex, event.params.result.workerIndex);
|
||||
event.params.result.workerIndex += this._baseWorkerIndex;
|
||||
}
|
||||
}
|
||||
usedWorkers() {
|
||||
return this._maxWorkerIndex + 1;
|
||||
}
|
||||
}
|
||||
class JsonEventPatchers {
|
||||
constructor() {
|
||||
this.patchers = [];
|
||||
}
|
||||
patchEvents(events) {
|
||||
for (const event of events) {
|
||||
for (const patcher of this.patchers)
|
||||
patcher.patchEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
class BlobModernizer {
|
||||
modernize(fromVersion, events) {
|
||||
const result = [];
|
||||
for (const event of events)
|
||||
result.push(...this._modernize(fromVersion, event));
|
||||
return result;
|
||||
}
|
||||
_modernize(fromVersion, event) {
|
||||
let events = [event];
|
||||
for (let version = fromVersion; version < import_blob.currentBlobReportVersion; ++version)
|
||||
events = this[`_modernize_${version}_to_${version + 1}`].call(this, events);
|
||||
return events;
|
||||
}
|
||||
_modernize_1_to_2(events) {
|
||||
return events.map((event) => {
|
||||
if (event.method === "onProject") {
|
||||
const modernizeSuite = (suite) => {
|
||||
const newSuites = suite.suites.map(modernizeSuite);
|
||||
const { suites, tests, ...remainder } = suite;
|
||||
return { entries: [...newSuites, ...tests], ...remainder };
|
||||
};
|
||||
const project = event.params.project;
|
||||
project.suites = project.suites.map(modernizeSuite);
|
||||
}
|
||||
return event;
|
||||
});
|
||||
}
|
||||
}
|
||||
const modernizer = new BlobModernizer();
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
createMergedReport
|
||||
});
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var multiplexer_exports = {};
|
||||
__export(multiplexer_exports, {
|
||||
Multiplexer: () => Multiplexer
|
||||
});
|
||||
module.exports = __toCommonJS(multiplexer_exports);
|
||||
class Multiplexer {
|
||||
constructor(reporters) {
|
||||
this._reporters = reporters;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onConfigure?.(config));
|
||||
}
|
||||
onBegin(suite) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onBegin?.(suite));
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onTestBegin?.(test, result));
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onStdOut?.(chunk, test, result));
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onStdErr?.(chunk, test, result));
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
for (const reporter of this._reporters)
|
||||
await wrapAsync(() => reporter.onTestPaused?.(test, result));
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onTestEnd?.(test, result));
|
||||
}
|
||||
onReportConfigure(params) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onReportConfigure?.(params));
|
||||
}
|
||||
onReportEnd(params) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onReportEnd?.(params));
|
||||
}
|
||||
async onEnd(result) {
|
||||
for (const reporter of this._reporters) {
|
||||
const outResult = await wrapAsync(() => reporter.onEnd?.(result));
|
||||
if (outResult?.status)
|
||||
result.status = outResult.status;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async onExit() {
|
||||
for (const reporter of this._reporters)
|
||||
await wrapAsync(() => reporter.onExit?.());
|
||||
}
|
||||
onError(error) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onError?.(error));
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onStepBegin?.(test, result, step));
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onStepEnd?.(test, result, step));
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporters.some((r) => {
|
||||
let prints = false;
|
||||
wrap(() => prints = r.printsToStdio ? r.printsToStdio() : true);
|
||||
return prints;
|
||||
});
|
||||
}
|
||||
}
|
||||
async function wrapAsync(callback) {
|
||||
try {
|
||||
return await callback();
|
||||
} catch (e) {
|
||||
console.error("Error in reporter", e);
|
||||
}
|
||||
}
|
||||
function wrap(callback) {
|
||||
try {
|
||||
callback();
|
||||
} catch (e) {
|
||||
console.error("Error in reporter", e);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Multiplexer
|
||||
});
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var reporterV2_exports = {};
|
||||
__export(reporterV2_exports, {
|
||||
wrapReporterAsV2: () => wrapReporterAsV2
|
||||
});
|
||||
module.exports = __toCommonJS(reporterV2_exports);
|
||||
function wrapReporterAsV2(reporter) {
|
||||
try {
|
||||
if ("version" in reporter && reporter.version() === "v2")
|
||||
return reporter;
|
||||
} catch (e) {
|
||||
}
|
||||
return new ReporterV2Wrapper(reporter);
|
||||
}
|
||||
class ReporterV2Wrapper {
|
||||
constructor(reporter) {
|
||||
this._deferred = [];
|
||||
this._reporter = reporter;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this._config = config;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this._reporter.onBegin?.(this._config, suite);
|
||||
const deferred = this._deferred;
|
||||
this._deferred = null;
|
||||
for (const item of deferred) {
|
||||
if (item.error)
|
||||
this.onError(item.error);
|
||||
if (item.stdout)
|
||||
this.onStdOut(item.stdout.chunk, item.stdout.test, item.stdout.result);
|
||||
if (item.stderr)
|
||||
this.onStdErr(item.stderr.chunk, item.stderr.test, item.stderr.result);
|
||||
}
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
this._reporter.onTestBegin?.(test, result);
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
if (this._deferred) {
|
||||
this._deferred.push({ stdout: { chunk, test, result } });
|
||||
return;
|
||||
}
|
||||
this._reporter.onStdOut?.(chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
if (this._deferred) {
|
||||
this._deferred.push({ stderr: { chunk, test, result } });
|
||||
return;
|
||||
}
|
||||
this._reporter.onStdErr?.(chunk, test, result);
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
}
|
||||
async onEnd(result) {
|
||||
return await this._reporter.onEnd?.(result);
|
||||
}
|
||||
async onExit() {
|
||||
await this._reporter.onExit?.();
|
||||
}
|
||||
onError(error) {
|
||||
if (this._deferred) {
|
||||
this._deferred.push({ error });
|
||||
return;
|
||||
}
|
||||
this._reporter.onError?.(error);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
this._reporter.onStepBegin?.(test, result, step);
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
printsToStdio() {
|
||||
return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true;
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
wrapReporterAsV2
|
||||
});
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var teleEmitter_exports = {};
|
||||
__export(teleEmitter_exports, {
|
||||
TeleReporterEmitter: () => TeleReporterEmitter
|
||||
});
|
||||
module.exports = __toCommonJS(teleEmitter_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_teleReceiver = require("../isomorphic/teleReceiver");
|
||||
class TeleReporterEmitter {
|
||||
constructor(messageSink, options = {}) {
|
||||
this._resultKnownAttachmentCounts = /* @__PURE__ */ new Map();
|
||||
this._resultKnownErrorCounts = /* @__PURE__ */ new Map();
|
||||
// In case there is blob reporter and UI mode, make sure one doesn't override
|
||||
// the id assigned by the other.
|
||||
this._idSymbol = Symbol("id");
|
||||
this._messageSink = messageSink;
|
||||
this._emitterOptions = options;
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
onConfigure(config) {
|
||||
this._rootDir = config.rootDir;
|
||||
this._messageSink({ method: "onConfigure", params: { config: this._serializeConfig(config) } });
|
||||
}
|
||||
onBegin(suite) {
|
||||
const projects = suite.suites.map((projectSuite) => this._serializeProject(projectSuite));
|
||||
for (const project of projects)
|
||||
this._messageSink({ method: "onProject", params: { project } });
|
||||
this._messageSink({ method: "onBegin", params: void 0 });
|
||||
}
|
||||
onTestBegin(test, result) {
|
||||
result[this._idSymbol] = (0, import_utils.createGuid)();
|
||||
this._messageSink({
|
||||
method: "onTestBegin",
|
||||
params: {
|
||||
testId: test.id,
|
||||
result: this._serializeResultStart(result)
|
||||
}
|
||||
});
|
||||
}
|
||||
async onTestPaused(test, result) {
|
||||
const resultId = result[this._idSymbol];
|
||||
this._resultKnownErrorCounts.set(resultId, result.errors.length);
|
||||
this._messageSink({
|
||||
method: "onTestPaused",
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId,
|
||||
errors: result.errors
|
||||
}
|
||||
});
|
||||
await new Promise(() => {
|
||||
});
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
const testEnd = {
|
||||
testId: test.id,
|
||||
expectedStatus: test.expectedStatus,
|
||||
timeout: test.timeout,
|
||||
annotations: []
|
||||
};
|
||||
this._sendNewAttachments(result, test.id);
|
||||
this._messageSink({
|
||||
method: "onTestEnd",
|
||||
params: {
|
||||
test: testEnd,
|
||||
result: this._serializeResultEnd(result)
|
||||
}
|
||||
});
|
||||
const resultId = result[this._idSymbol];
|
||||
this._resultKnownAttachmentCounts.delete(resultId);
|
||||
this._resultKnownErrorCounts.delete(resultId);
|
||||
}
|
||||
onStepBegin(test, result, step) {
|
||||
step[this._idSymbol] = (0, import_utils.createGuid)();
|
||||
this._messageSink({
|
||||
method: "onStepBegin",
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId: result[this._idSymbol],
|
||||
step: this._serializeStepStart(step)
|
||||
}
|
||||
});
|
||||
}
|
||||
onStepEnd(test, result, step) {
|
||||
const resultId = result[this._idSymbol];
|
||||
this._sendNewAttachments(result, test.id);
|
||||
this._messageSink({
|
||||
method: "onStepEnd",
|
||||
params: {
|
||||
testId: test.id,
|
||||
resultId,
|
||||
step: this._serializeStepEnd(step, result)
|
||||
}
|
||||
});
|
||||
}
|
||||
onError(error) {
|
||||
this._messageSink({
|
||||
method: "onError",
|
||||
params: { error }
|
||||
});
|
||||
}
|
||||
onStdOut(chunk, test, result) {
|
||||
this._onStdIO("stdout", chunk, test, result);
|
||||
}
|
||||
onStdErr(chunk, test, result) {
|
||||
this._onStdIO("stderr", chunk, test, result);
|
||||
}
|
||||
_onStdIO(type, chunk, test, result) {
|
||||
if (this._emitterOptions.omitOutput)
|
||||
return;
|
||||
const isBase64 = typeof chunk !== "string";
|
||||
const data = isBase64 ? chunk.toString("base64") : chunk;
|
||||
this._messageSink({
|
||||
method: "onStdIO",
|
||||
params: { testId: test?.id, resultId: result ? result[this._idSymbol] : void 0, type, data, isBase64 }
|
||||
});
|
||||
}
|
||||
async onEnd(result) {
|
||||
const resultPayload = {
|
||||
status: result.status,
|
||||
startTime: result.startTime.getTime(),
|
||||
duration: result.duration
|
||||
};
|
||||
this._messageSink({
|
||||
method: "onEnd",
|
||||
params: {
|
||||
result: resultPayload
|
||||
}
|
||||
});
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
_serializeConfig(config) {
|
||||
return {
|
||||
configFile: this._relativePath(config.configFile),
|
||||
globalTimeout: config.globalTimeout,
|
||||
maxFailures: config.maxFailures,
|
||||
metadata: config.metadata,
|
||||
rootDir: config.rootDir,
|
||||
shard: config.shard,
|
||||
version: config.version,
|
||||
workers: config.workers,
|
||||
globalSetup: config.globalSetup,
|
||||
globalTeardown: config.globalTeardown,
|
||||
tags: config.tags,
|
||||
webServer: config.webServer
|
||||
};
|
||||
}
|
||||
_serializeProject(suite) {
|
||||
const project = suite.project();
|
||||
const report = {
|
||||
metadata: project.metadata,
|
||||
name: project.name,
|
||||
outputDir: this._relativePath(project.outputDir),
|
||||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
testDir: this._relativePath(project.testDir),
|
||||
testIgnore: (0, import_teleReceiver.serializeRegexPatterns)(project.testIgnore),
|
||||
testMatch: (0, import_teleReceiver.serializeRegexPatterns)(project.testMatch),
|
||||
timeout: project.timeout,
|
||||
suites: suite.suites.map((fileSuite) => {
|
||||
return this._serializeSuite(fileSuite);
|
||||
}),
|
||||
grep: (0, import_teleReceiver.serializeRegexPatterns)(project.grep),
|
||||
grepInvert: (0, import_teleReceiver.serializeRegexPatterns)(project.grepInvert || []),
|
||||
dependencies: project.dependencies,
|
||||
snapshotDir: this._relativePath(project.snapshotDir),
|
||||
teardown: project.teardown,
|
||||
ignoreSnapshots: project.ignoreSnapshots ? true : void 0,
|
||||
use: this._serializeProjectUseOptions(project.use)
|
||||
};
|
||||
return report;
|
||||
}
|
||||
_serializeProjectUseOptions(use) {
|
||||
return {
|
||||
testIdAttribute: use.testIdAttribute
|
||||
};
|
||||
}
|
||||
_serializeSuite(suite) {
|
||||
const result = {
|
||||
title: suite.title,
|
||||
location: this._relativeLocation(suite.location),
|
||||
entries: suite.entries().map((e) => {
|
||||
if (e.type === "test")
|
||||
return this._serializeTest(e);
|
||||
return this._serializeSuite(e);
|
||||
})
|
||||
};
|
||||
return result;
|
||||
}
|
||||
_serializeTest(test) {
|
||||
return {
|
||||
testId: test.id,
|
||||
title: test.title,
|
||||
location: this._relativeLocation(test.location),
|
||||
retries: test.retries,
|
||||
tags: test.tags,
|
||||
repeatEachIndex: test.repeatEachIndex,
|
||||
annotations: this._relativeAnnotationLocations(test.annotations)
|
||||
};
|
||||
}
|
||||
_serializeResultStart(result) {
|
||||
return {
|
||||
id: result[this._idSymbol],
|
||||
retry: result.retry,
|
||||
workerIndex: result.workerIndex,
|
||||
parallelIndex: result.parallelIndex,
|
||||
startTime: +result.startTime
|
||||
};
|
||||
}
|
||||
_serializeResultEnd(result) {
|
||||
const id = result[this._idSymbol];
|
||||
return {
|
||||
id,
|
||||
duration: result.duration,
|
||||
status: result.status,
|
||||
errors: this._resultKnownErrorCounts.has(id) ? result.errors.slice(this._resultKnownAttachmentCounts.get(id)) : result.errors,
|
||||
annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : void 0
|
||||
};
|
||||
}
|
||||
_sendNewAttachments(result, testId) {
|
||||
const resultId = result[this._idSymbol];
|
||||
const knownAttachmentCount = this._resultKnownAttachmentCounts.get(resultId) ?? 0;
|
||||
if (result.attachments.length > knownAttachmentCount) {
|
||||
this._messageSink({
|
||||
method: "onAttach",
|
||||
params: {
|
||||
testId,
|
||||
resultId,
|
||||
attachments: this._serializeAttachments(result.attachments.slice(knownAttachmentCount))
|
||||
}
|
||||
});
|
||||
}
|
||||
this._resultKnownAttachmentCounts.set(resultId, result.attachments.length);
|
||||
}
|
||||
_serializeAttachments(attachments) {
|
||||
return attachments.map((a) => {
|
||||
const { body, ...rest } = a;
|
||||
return {
|
||||
...rest,
|
||||
// There is no Buffer in the browser, so there is no point in sending the data there.
|
||||
base64: body && !this._emitterOptions.omitBuffers ? body.toString("base64") : void 0
|
||||
};
|
||||
});
|
||||
}
|
||||
_serializeStepStart(step) {
|
||||
return {
|
||||
id: step[this._idSymbol],
|
||||
parentStepId: step.parent?.[this._idSymbol],
|
||||
title: step.title,
|
||||
category: step.category,
|
||||
startTime: +step.startTime,
|
||||
location: this._relativeLocation(step.location)
|
||||
};
|
||||
}
|
||||
_serializeStepEnd(step, result) {
|
||||
return {
|
||||
id: step[this._idSymbol],
|
||||
duration: step.duration,
|
||||
error: step.error,
|
||||
attachments: step.attachments.length ? step.attachments.map((a) => result.attachments.indexOf(a)) : void 0,
|
||||
annotations: step.annotations.length ? this._relativeAnnotationLocations(step.annotations) : void 0
|
||||
};
|
||||
}
|
||||
_relativeAnnotationLocations(annotations) {
|
||||
return annotations.map((annotation) => ({
|
||||
...annotation,
|
||||
location: annotation.location ? this._relativeLocation(annotation.location) : void 0
|
||||
}));
|
||||
}
|
||||
_relativeLocation(location) {
|
||||
if (!location)
|
||||
return location;
|
||||
return {
|
||||
...location,
|
||||
file: this._relativePath(location.file)
|
||||
};
|
||||
}
|
||||
_relativePath(absolutePath) {
|
||||
if (!absolutePath)
|
||||
return absolutePath;
|
||||
return import_path.default.relative(this._rootDir, absolutePath);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
TeleReporterEmitter
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var blobV1_exports = {};
|
||||
module.exports = __toCommonJS(blobV1_exports);
|
||||
+522
@@ -0,0 +1,522 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var dispatcher_exports = {};
|
||||
__export(dispatcher_exports, {
|
||||
Dispatcher: () => Dispatcher
|
||||
});
|
||||
module.exports = __toCommonJS(dispatcher_exports);
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_rebase = require("./rebase");
|
||||
var import_workerHost = require("./workerHost");
|
||||
var import_ipc = require("../common/ipc");
|
||||
var import_internalReporter = require("../reporters/internalReporter");
|
||||
var import_util = require("../util");
|
||||
class Dispatcher {
|
||||
constructor(config, reporter, failureTracker) {
|
||||
// Worker slot is claimed when it has jobDispatcher assigned.
|
||||
this._workerSlots = [];
|
||||
this._queue = [];
|
||||
this._workerLimitPerProjectId = /* @__PURE__ */ new Map();
|
||||
this._queuedOrRunningHashCount = /* @__PURE__ */ new Map();
|
||||
this._finished = new import_utils.ManualPromise();
|
||||
this._isStopped = true;
|
||||
this._extraEnvByProjectId = /* @__PURE__ */ new Map();
|
||||
this._producedEnvByProjectId = /* @__PURE__ */ new Map();
|
||||
this._config = config;
|
||||
this._reporter = reporter;
|
||||
this._failureTracker = failureTracker;
|
||||
for (const project of config.projects) {
|
||||
if (project.workers)
|
||||
this._workerLimitPerProjectId.set(project.id, project.workers);
|
||||
}
|
||||
}
|
||||
_findFirstJobToRun() {
|
||||
for (let index = 0; index < this._queue.length; index++) {
|
||||
const job = this._queue[index];
|
||||
const projectIdWorkerLimit = this._workerLimitPerProjectId.get(job.projectId);
|
||||
if (!projectIdWorkerLimit)
|
||||
return index;
|
||||
const runningWorkersWithSameProjectId = this._workerSlots.filter((w) => w.jobDispatcher?.job.projectId === job.projectId).length;
|
||||
if (runningWorkersWithSameProjectId < projectIdWorkerLimit)
|
||||
return index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
_scheduleJob() {
|
||||
if (this._isStopped)
|
||||
return;
|
||||
const jobIndex = this._findFirstJobToRun();
|
||||
if (jobIndex === -1)
|
||||
return;
|
||||
const job = this._queue[jobIndex];
|
||||
let workerIndex = this._workerSlots.findIndex((w) => !w.jobDispatcher && w.worker && w.worker.hash() === job.workerHash && !w.worker.didSendStop());
|
||||
if (workerIndex === -1)
|
||||
workerIndex = this._workerSlots.findIndex((w) => !w.jobDispatcher);
|
||||
if (workerIndex === -1) {
|
||||
return;
|
||||
}
|
||||
this._queue.splice(jobIndex, 1);
|
||||
const jobDispatcher = new JobDispatcher(job, this._config, this._reporter, this._failureTracker, () => this.stop().catch(() => {
|
||||
}));
|
||||
this._workerSlots[workerIndex].jobDispatcher = jobDispatcher;
|
||||
void this._runJobInWorker(workerIndex, jobDispatcher).then(() => {
|
||||
this._workerSlots[workerIndex].jobDispatcher = void 0;
|
||||
this._checkFinished();
|
||||
this._scheduleJob();
|
||||
});
|
||||
}
|
||||
async _runJobInWorker(index, jobDispatcher) {
|
||||
const job = jobDispatcher.job;
|
||||
if (jobDispatcher.skipWholeJob())
|
||||
return;
|
||||
let worker = this._workerSlots[index].worker;
|
||||
if (worker && (worker.hash() !== job.workerHash || worker.didSendStop())) {
|
||||
await worker.stop();
|
||||
worker = void 0;
|
||||
if (this._isStopped)
|
||||
return;
|
||||
}
|
||||
let startError;
|
||||
if (!worker) {
|
||||
worker = this._createWorker(job, index, (0, import_ipc.serializeConfig)(this._config, true));
|
||||
this._workerSlots[index].worker = worker;
|
||||
worker.on("exit", () => this._workerSlots[index].worker = void 0);
|
||||
startError = await worker.start();
|
||||
if (this._isStopped)
|
||||
return;
|
||||
}
|
||||
if (startError)
|
||||
jobDispatcher.onExit(startError);
|
||||
else
|
||||
jobDispatcher.runInWorker(worker);
|
||||
const result = await jobDispatcher.jobResult;
|
||||
this._updateCounterForWorkerHash(job.workerHash, -1);
|
||||
if (result.didFail)
|
||||
void worker.stop(
|
||||
true
|
||||
/* didFail */
|
||||
);
|
||||
else if (this._isWorkerRedundant(worker))
|
||||
void worker.stop();
|
||||
if (!this._isStopped && result.newJob) {
|
||||
this._queue.unshift(result.newJob);
|
||||
this._updateCounterForWorkerHash(result.newJob.workerHash, 1);
|
||||
}
|
||||
}
|
||||
_checkFinished() {
|
||||
if (this._finished.isDone())
|
||||
return;
|
||||
if (this._queue.length && !this._isStopped)
|
||||
return;
|
||||
if (this._workerSlots.some((w) => !!w.jobDispatcher))
|
||||
return;
|
||||
this._finished.resolve();
|
||||
}
|
||||
_isWorkerRedundant(worker) {
|
||||
let workersWithSameHash = 0;
|
||||
for (const slot of this._workerSlots) {
|
||||
if (slot.worker && !slot.worker.didSendStop() && slot.worker.hash() === worker.hash())
|
||||
workersWithSameHash++;
|
||||
}
|
||||
return workersWithSameHash > this._queuedOrRunningHashCount.get(worker.hash());
|
||||
}
|
||||
_updateCounterForWorkerHash(hash, delta) {
|
||||
this._queuedOrRunningHashCount.set(hash, delta + (this._queuedOrRunningHashCount.get(hash) || 0));
|
||||
}
|
||||
async run(testGroups, extraEnvByProjectId) {
|
||||
this._extraEnvByProjectId = extraEnvByProjectId;
|
||||
this._queue = testGroups;
|
||||
for (const group of testGroups)
|
||||
this._updateCounterForWorkerHash(group.workerHash, 1);
|
||||
this._isStopped = false;
|
||||
this._workerSlots = [];
|
||||
if (this._failureTracker.hasReachedMaxFailures())
|
||||
void this.stop();
|
||||
for (let i = 0; i < this._config.config.workers; i++)
|
||||
this._workerSlots.push({});
|
||||
for (let i = 0; i < this._workerSlots.length; i++)
|
||||
this._scheduleJob();
|
||||
this._checkFinished();
|
||||
await this._finished;
|
||||
}
|
||||
_createWorker(testGroup, parallelIndex, loaderData) {
|
||||
const projectConfig = this._config.projects.find((p) => p.id === testGroup.projectId);
|
||||
const outputDir = projectConfig.project.outputDir;
|
||||
const worker = new import_workerHost.WorkerHost(testGroup, {
|
||||
parallelIndex,
|
||||
config: loaderData,
|
||||
extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {},
|
||||
outputDir,
|
||||
pauseOnError: this._failureTracker.pauseOnError(),
|
||||
pauseAtEnd: this._failureTracker.pauseAtEnd(projectConfig)
|
||||
});
|
||||
const handleOutput = (params) => {
|
||||
const chunk = chunkFromParams(params);
|
||||
if (worker.didFail()) {
|
||||
return { chunk };
|
||||
}
|
||||
const currentlyRunning = this._workerSlots[parallelIndex].jobDispatcher?.currentlyRunning();
|
||||
if (!currentlyRunning)
|
||||
return { chunk };
|
||||
return { chunk, test: currentlyRunning.test, result: currentlyRunning.result };
|
||||
};
|
||||
worker.on("stdOut", (params) => {
|
||||
const { chunk, test, result } = handleOutput(params);
|
||||
result?.stdout.push(chunk);
|
||||
this._reporter.onStdOut?.(chunk, test, result);
|
||||
});
|
||||
worker.on("stdErr", (params) => {
|
||||
const { chunk, test, result } = handleOutput(params);
|
||||
result?.stderr.push(chunk);
|
||||
this._reporter.onStdErr?.(chunk, test, result);
|
||||
});
|
||||
worker.on("teardownErrors", (params) => {
|
||||
this._failureTracker.onWorkerError();
|
||||
for (const error of params.fatalErrors)
|
||||
this._reporter.onError?.(error);
|
||||
});
|
||||
worker.on("exit", () => {
|
||||
const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {};
|
||||
this._producedEnvByProjectId.set(testGroup.projectId, { ...producedEnv, ...worker.producedEnv() });
|
||||
});
|
||||
return worker;
|
||||
}
|
||||
producedEnvByProjectId() {
|
||||
return this._producedEnvByProjectId;
|
||||
}
|
||||
async stop() {
|
||||
if (this._isStopped)
|
||||
return;
|
||||
this._isStopped = true;
|
||||
await Promise.all(this._workerSlots.map(({ worker }) => worker?.stop()));
|
||||
this._checkFinished();
|
||||
}
|
||||
}
|
||||
class JobDispatcher {
|
||||
constructor(job, config, reporter, failureTracker, stopCallback) {
|
||||
this.jobResult = new import_utils.ManualPromise();
|
||||
this._listeners = [];
|
||||
this._failedTests = /* @__PURE__ */ new Set();
|
||||
this._failedWithNonRetriableError = /* @__PURE__ */ new Set();
|
||||
this._remainingByTestId = /* @__PURE__ */ new Map();
|
||||
this._dataByTestId = /* @__PURE__ */ new Map();
|
||||
this._parallelIndex = 0;
|
||||
this._workerIndex = 0;
|
||||
this.job = job;
|
||||
this._config = config;
|
||||
this._reporter = reporter;
|
||||
this._failureTracker = failureTracker;
|
||||
this._stopCallback = stopCallback;
|
||||
this._remainingByTestId = new Map(this.job.tests.map((e) => [e.id, e]));
|
||||
}
|
||||
_onTestBegin(params) {
|
||||
const test = this._remainingByTestId.get(params.testId);
|
||||
if (!test) {
|
||||
return;
|
||||
}
|
||||
const result = test._appendTestResult();
|
||||
this._dataByTestId.set(test.id, { test, result, steps: /* @__PURE__ */ new Map() });
|
||||
result.parallelIndex = this._parallelIndex;
|
||||
result.workerIndex = this._workerIndex;
|
||||
result.startTime = new Date(params.startWallTime);
|
||||
this._reporter.onTestBegin?.(test, result);
|
||||
this._currentlyRunning = { test, result };
|
||||
}
|
||||
_onTestEnd(params) {
|
||||
if (this._failureTracker.hasReachedMaxFailures()) {
|
||||
params.status = "interrupted";
|
||||
params.errors = [];
|
||||
}
|
||||
const data = this._dataByTestId.get(params.testId);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
this._dataByTestId.delete(params.testId);
|
||||
this._remainingByTestId.delete(params.testId);
|
||||
const { result, test } = data;
|
||||
result.duration = params.duration;
|
||||
result.errors = params.errors;
|
||||
result.error = result.errors[0];
|
||||
result.status = params.status;
|
||||
result.annotations = params.annotations;
|
||||
test.annotations = [...params.annotations];
|
||||
test.expectedStatus = params.expectedStatus;
|
||||
test.timeout = params.timeout;
|
||||
const isFailure = result.status !== "skipped" && result.status !== test.expectedStatus;
|
||||
if (isFailure)
|
||||
this._failedTests.add(test);
|
||||
if (params.hasNonRetriableError)
|
||||
this._addNonretriableTestAndSerialModeParents(test);
|
||||
this._reportTestEnd(test, result);
|
||||
this._currentlyRunning = void 0;
|
||||
}
|
||||
_addNonretriableTestAndSerialModeParents(test) {
|
||||
this._failedWithNonRetriableError.add(test);
|
||||
for (let parent = test.parent; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === "serial")
|
||||
this._failedWithNonRetriableError.add(parent);
|
||||
}
|
||||
}
|
||||
_onStepBegin(params) {
|
||||
const data = this._dataByTestId.get(params.testId);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const { result, steps, test } = data;
|
||||
const parentStep = params.parentStepId ? steps.get(params.parentStepId) : void 0;
|
||||
const step = {
|
||||
title: params.title,
|
||||
titlePath: () => {
|
||||
const parentPath = parentStep?.titlePath() || [];
|
||||
return [...parentPath, params.title];
|
||||
},
|
||||
parent: parentStep,
|
||||
category: params.category,
|
||||
startTime: new Date(params.wallTime),
|
||||
duration: -1,
|
||||
steps: [],
|
||||
attachments: [],
|
||||
annotations: [],
|
||||
location: params.location
|
||||
};
|
||||
steps.set(params.stepId, step);
|
||||
(parentStep || result).steps.push(step);
|
||||
this._reporter.onStepBegin?.(test, result, step);
|
||||
}
|
||||
_onStepEnd(params) {
|
||||
const data = this._dataByTestId.get(params.testId);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const { result, steps, test } = data;
|
||||
const step = steps.get(params.stepId);
|
||||
if (!step) {
|
||||
this._reporter.onStdErr?.("Internal error: step end without step begin: " + params.stepId, test, result);
|
||||
return;
|
||||
}
|
||||
step.duration = params.wallTime - step.startTime.getTime();
|
||||
if (params.error)
|
||||
step.error = params.error;
|
||||
if (params.suggestedRebaseline)
|
||||
(0, import_rebase.addSuggestedRebaseline)(step.location, params.suggestedRebaseline);
|
||||
step.annotations = params.annotations;
|
||||
steps.delete(params.stepId);
|
||||
this._reporter.onStepEnd?.(test, result, step);
|
||||
}
|
||||
_onAttach(params) {
|
||||
const data = this._dataByTestId.get(params.testId);
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
const attachment = {
|
||||
name: params.name,
|
||||
path: params.path,
|
||||
contentType: params.contentType,
|
||||
body: params.body !== void 0 ? Buffer.from(params.body, "base64") : void 0
|
||||
};
|
||||
data.result.attachments.push(attachment);
|
||||
if (params.stepId) {
|
||||
const step = data.steps.get(params.stepId);
|
||||
if (step)
|
||||
step.attachments.push(attachment);
|
||||
else
|
||||
this._reporter.onStdErr?.("Internal error: step id not found: " + params.stepId);
|
||||
}
|
||||
}
|
||||
_failTestWithErrors(test, errors) {
|
||||
const runData = this._dataByTestId.get(test.id);
|
||||
let result;
|
||||
if (runData) {
|
||||
result = runData.result;
|
||||
} else {
|
||||
result = test._appendTestResult();
|
||||
this._reporter.onTestBegin?.(test, result);
|
||||
}
|
||||
result.errors = [...errors];
|
||||
result.error = result.errors[0];
|
||||
result.status = errors.length ? "failed" : "skipped";
|
||||
this._reportTestEnd(test, result);
|
||||
this._failedTests.add(test);
|
||||
}
|
||||
_massSkipTestsFromRemaining(testIds, errors) {
|
||||
for (const test of this._remainingByTestId.values()) {
|
||||
if (!testIds.has(test.id))
|
||||
continue;
|
||||
if (!this._failureTracker.hasReachedMaxFailures()) {
|
||||
this._failTestWithErrors(test, errors);
|
||||
errors = [];
|
||||
}
|
||||
this._remainingByTestId.delete(test.id);
|
||||
}
|
||||
if (errors.length) {
|
||||
this._failureTracker.onWorkerError();
|
||||
for (const error of errors)
|
||||
this._reporter.onError?.(error);
|
||||
}
|
||||
}
|
||||
_onDone(params) {
|
||||
if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError && !params.stoppedDueToUnhandledErrorInTestFail) {
|
||||
this._finished({ didFail: false });
|
||||
return;
|
||||
}
|
||||
for (const testId of params.fatalUnknownTestIds || []) {
|
||||
const test = this._remainingByTestId.get(testId);
|
||||
if (test) {
|
||||
this._remainingByTestId.delete(testId);
|
||||
this._failTestWithErrors(test, [{ message: `Test not found in the worker process. Make sure test title does not change.` }]);
|
||||
}
|
||||
}
|
||||
if (params.fatalErrors.length) {
|
||||
this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), params.fatalErrors);
|
||||
}
|
||||
this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []);
|
||||
if (params.unexpectedExitError) {
|
||||
if (this._currentlyRunning)
|
||||
this._massSkipTestsFromRemaining(/* @__PURE__ */ new Set([this._currentlyRunning.test.id]), [params.unexpectedExitError]);
|
||||
else
|
||||
this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), [params.unexpectedExitError]);
|
||||
}
|
||||
const retryCandidates = /* @__PURE__ */ new Set();
|
||||
const serialSuitesWithFailures = /* @__PURE__ */ new Set();
|
||||
for (const failedTest of this._failedTests) {
|
||||
if (this._failedWithNonRetriableError.has(failedTest))
|
||||
continue;
|
||||
retryCandidates.add(failedTest);
|
||||
let outermostSerialSuite;
|
||||
for (let parent = failedTest.parent; parent; parent = parent.parent) {
|
||||
if (parent._parallelMode === "serial")
|
||||
outermostSerialSuite = parent;
|
||||
}
|
||||
if (outermostSerialSuite && !this._failedWithNonRetriableError.has(outermostSerialSuite))
|
||||
serialSuitesWithFailures.add(outermostSerialSuite);
|
||||
}
|
||||
const testsBelongingToSomeSerialSuiteWithFailures = [...this._remainingByTestId.values()].filter((test) => {
|
||||
let parent = test.parent;
|
||||
while (parent && !serialSuitesWithFailures.has(parent))
|
||||
parent = parent.parent;
|
||||
return !!parent;
|
||||
});
|
||||
this._massSkipTestsFromRemaining(new Set(testsBelongingToSomeSerialSuiteWithFailures.map((test) => test.id)), []);
|
||||
for (const serialSuite of serialSuitesWithFailures) {
|
||||
serialSuite.allTests().forEach((test) => retryCandidates.add(test));
|
||||
}
|
||||
const remaining = [...this._remainingByTestId.values()];
|
||||
for (const test of retryCandidates) {
|
||||
if (test.results.length < test.retries + 1)
|
||||
remaining.push(test);
|
||||
}
|
||||
const newJob = remaining.length ? { ...this.job, tests: remaining } : void 0;
|
||||
this._finished({ didFail: true, newJob });
|
||||
}
|
||||
onExit(data) {
|
||||
const unexpectedExitError = data.unexpectedly ? {
|
||||
message: `Error: worker process exited unexpectedly (code=${data.code}, signal=${data.signal})`
|
||||
} : void 0;
|
||||
this._onDone({ skipTestsDueToSetupFailure: [], fatalErrors: [], unexpectedExitError });
|
||||
}
|
||||
_finished(result) {
|
||||
import_utils.eventsHelper.removeEventListeners(this._listeners);
|
||||
this.jobResult.resolve(result);
|
||||
}
|
||||
runInWorker(worker) {
|
||||
this._parallelIndex = worker.parallelIndex;
|
||||
this._workerIndex = worker.workerIndex;
|
||||
const runPayload = {
|
||||
file: this.job.requireFile,
|
||||
entries: this.job.tests.map((test) => {
|
||||
return { testId: test.id, retry: test.results.length };
|
||||
})
|
||||
};
|
||||
worker.runTestGroup(runPayload);
|
||||
this._listeners = [
|
||||
import_utils.eventsHelper.addEventListener(worker, "testBegin", this._onTestBegin.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "testEnd", this._onTestEnd.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "stepBegin", this._onStepBegin.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "stepEnd", this._onStepEnd.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "attach", this._onAttach.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "testPaused", this._onTestPaused.bind(this, worker)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "done", this._onDone.bind(this)),
|
||||
import_utils.eventsHelper.addEventListener(worker, "exit", this.onExit.bind(this))
|
||||
];
|
||||
}
|
||||
_onTestPaused(worker, params) {
|
||||
const data = this._dataByTestId.get(params.testId);
|
||||
if (!data)
|
||||
return;
|
||||
const { result, test } = data;
|
||||
const sendMessage = async (message) => {
|
||||
try {
|
||||
if (this.jobResult.isDone())
|
||||
throw new Error("Test has already stopped");
|
||||
const response = await worker.sendCustomMessage({ testId: test.id, request: message.request });
|
||||
if (response.error)
|
||||
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, response.error);
|
||||
return response;
|
||||
} catch (e) {
|
||||
const error = (0, import_util.serializeError)(e);
|
||||
(0, import_internalReporter.addLocationAndSnippetToError)(this._config.config, error);
|
||||
return { response: void 0, error };
|
||||
}
|
||||
};
|
||||
result.status = params.status;
|
||||
result.errors = params.errors;
|
||||
result.error = result.errors[0];
|
||||
void this._reporter.onTestPaused?.(test, result).then(() => {
|
||||
worker.sendResume({});
|
||||
});
|
||||
this._failureTracker.onTestPaused?.({ ...params, sendMessage });
|
||||
}
|
||||
skipWholeJob() {
|
||||
const allTestsSkipped = this.job.tests.every((test) => test.expectedStatus === "skipped");
|
||||
if (allTestsSkipped && !this._failureTracker.hasReachedMaxFailures()) {
|
||||
for (const test of this.job.tests) {
|
||||
const result = test._appendTestResult();
|
||||
this._reporter.onTestBegin?.(test, result);
|
||||
result.status = "skipped";
|
||||
result.annotations = [...test.annotations];
|
||||
this._reportTestEnd(test, result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
currentlyRunning() {
|
||||
return this._currentlyRunning;
|
||||
}
|
||||
_reportTestEnd(test, result) {
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
const hadMaxFailures = this._failureTracker.hasReachedMaxFailures();
|
||||
this._failureTracker.onTestEnd(test, result);
|
||||
if (this._failureTracker.hasReachedMaxFailures()) {
|
||||
this._stopCallback();
|
||||
if (!hadMaxFailures)
|
||||
this._reporter.onError?.({ message: import_utils2.colors.red(`Testing stopped early after ${this._failureTracker.maxFailures()} maximum allowed failures.`) });
|
||||
}
|
||||
}
|
||||
}
|
||||
function chunkFromParams(params) {
|
||||
if (typeof params.text === "string")
|
||||
return params.text;
|
||||
return Buffer.from(params.buffer, "base64");
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
Dispatcher
|
||||
});
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var failureTracker_exports = {};
|
||||
__export(failureTracker_exports, {
|
||||
FailureTracker: () => FailureTracker
|
||||
});
|
||||
module.exports = __toCommonJS(failureTracker_exports);
|
||||
class FailureTracker {
|
||||
constructor(config, options) {
|
||||
this._failureCount = 0;
|
||||
this._hasWorkerErrors = false;
|
||||
this._topLevelProjects = [];
|
||||
this._config = config;
|
||||
this._pauseOnError = !!options?.pauseOnError;
|
||||
this._pauseAtEnd = !!options?.pauseAtEnd;
|
||||
}
|
||||
onRootSuite(rootSuite, topLevelProjects) {
|
||||
this._rootSuite = rootSuite;
|
||||
this._topLevelProjects = topLevelProjects;
|
||||
}
|
||||
onTestEnd(test, result) {
|
||||
if (test.outcome() === "unexpected" && test.results.length > test.retries)
|
||||
++this._failureCount;
|
||||
}
|
||||
onWorkerError() {
|
||||
this._hasWorkerErrors = true;
|
||||
}
|
||||
pauseOnError() {
|
||||
return this._pauseOnError;
|
||||
}
|
||||
pauseAtEnd(inProject) {
|
||||
return this._topLevelProjects.includes(inProject) && this._pauseAtEnd;
|
||||
}
|
||||
hasReachedMaxFailures() {
|
||||
return this.maxFailures() > 0 && this._failureCount >= this.maxFailures();
|
||||
}
|
||||
hasWorkerErrors() {
|
||||
return this._hasWorkerErrors;
|
||||
}
|
||||
result() {
|
||||
return this._hasWorkerErrors || this.hasReachedMaxFailures() || this.hasFailedTests() || this._config.failOnFlakyTests && this.hasFlakyTests() ? "failed" : "passed";
|
||||
}
|
||||
hasFailedTests() {
|
||||
return this._rootSuite?.allTests().some((test) => !test.ok());
|
||||
}
|
||||
hasFlakyTests() {
|
||||
return this._rootSuite?.allTests().some((test) => test.outcome() === "flaky");
|
||||
}
|
||||
maxFailures() {
|
||||
return this._config.config.maxFailures;
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
FailureTracker
|
||||
});
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var lastRun_exports = {};
|
||||
__export(lastRun_exports, {
|
||||
LastRunReporter: () => LastRunReporter
|
||||
});
|
||||
module.exports = __toCommonJS(lastRun_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_projectUtils = require("./projectUtils");
|
||||
class LastRunReporter {
|
||||
constructor(config) {
|
||||
this._config = config;
|
||||
const [project] = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
||||
if (project)
|
||||
this._lastRunFile = import_path.default.join(project.project.outputDir, ".last-run.json");
|
||||
}
|
||||
async filterLastFailed() {
|
||||
if (!this._lastRunFile)
|
||||
return;
|
||||
try {
|
||||
const lastRunInfo = JSON.parse(await import_fs.default.promises.readFile(this._lastRunFile, "utf8"));
|
||||
const failedTestIds = new Set(lastRunInfo.failedTests);
|
||||
this._config.postShardTestFilters.push((test) => failedTestIds.has(test.id));
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
version() {
|
||||
return "v2";
|
||||
}
|
||||
printsToStdio() {
|
||||
return false;
|
||||
}
|
||||
onBegin(suite) {
|
||||
this._suite = suite;
|
||||
}
|
||||
async onEnd(result) {
|
||||
if (!this._lastRunFile || this._config.cliListOnly)
|
||||
return;
|
||||
const lastRunInfo = {
|
||||
status: result.status,
|
||||
failedTests: this._suite?.allTests().filter((t) => !t.ok()).map((t) => t.id) || []
|
||||
};
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(this._lastRunFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(this._lastRunFile, JSON.stringify(lastRunInfo, void 0, 2));
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
LastRunReporter
|
||||
});
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var loadUtils_exports = {};
|
||||
__export(loadUtils_exports, {
|
||||
collectProjectsAndTestFiles: () => collectProjectsAndTestFiles,
|
||||
createRootSuite: () => createRootSuite,
|
||||
loadFileSuites: () => loadFileSuites,
|
||||
loadGlobalHook: () => loadGlobalHook,
|
||||
loadReporter: () => loadReporter,
|
||||
loadTestList: () => loadTestList
|
||||
});
|
||||
module.exports = __toCommonJS(loadUtils_exports);
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_loaderHost = require("./loaderHost");
|
||||
var import_util = require("../util");
|
||||
var import_projectUtils = require("./projectUtils");
|
||||
var import_testGroups = require("./testGroups");
|
||||
var import_suiteUtils = require("../common/suiteUtils");
|
||||
var import_test = require("../common/test");
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
var import_transform = require("../transform/transform");
|
||||
var import_utilsBundle = require("../utilsBundle");
|
||||
async function collectProjectsAndTestFiles(testRun, doNotRunTestsOutsideProjectFilter) {
|
||||
const config = testRun.config;
|
||||
const fsCache = /* @__PURE__ */ new Map();
|
||||
const sourceMapCache = /* @__PURE__ */ new Map();
|
||||
const allFilesForProject = /* @__PURE__ */ new Map();
|
||||
const filteredProjects = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
||||
for (const project of filteredProjects) {
|
||||
const files = await (0, import_projectUtils.collectFilesForProject)(project, fsCache);
|
||||
allFilesForProject.set(project, files);
|
||||
}
|
||||
const filesToRunByProject = /* @__PURE__ */ new Map();
|
||||
for (const [project, files] of allFilesForProject) {
|
||||
const matchedFiles = files.filter((file) => {
|
||||
if (!config.loadFileFilters.length) {
|
||||
return true;
|
||||
}
|
||||
const hasMatchingSources = sourceMapSources(file, sourceMapCache).some((source) => {
|
||||
const matchesAllFileFilters = config.loadFileFilters.every((filter) => filter(source));
|
||||
return matchesAllFileFilters;
|
||||
});
|
||||
return hasMatchingSources;
|
||||
});
|
||||
const filteredFiles = matchedFiles.filter(Boolean);
|
||||
filesToRunByProject.set(project, filteredFiles);
|
||||
}
|
||||
const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filesToRunByProject.keys()]);
|
||||
for (const [project, type] of projectClosure) {
|
||||
if (type === "dependency") {
|
||||
const treatProjectAsEmpty = doNotRunTestsOutsideProjectFilter && !filteredProjects.includes(project);
|
||||
const files = treatProjectAsEmpty ? [] : allFilesForProject.get(project) || await (0, import_projectUtils.collectFilesForProject)(project, fsCache);
|
||||
filesToRunByProject.set(project, files);
|
||||
}
|
||||
}
|
||||
testRun.projectFiles = filesToRunByProject;
|
||||
testRun.projectSuites = /* @__PURE__ */ new Map();
|
||||
}
|
||||
async function loadFileSuites(testRun, mode, errors) {
|
||||
const config = testRun.config;
|
||||
const allTestFiles = /* @__PURE__ */ new Set();
|
||||
for (const files of testRun.projectFiles.values())
|
||||
files.forEach((file) => allTestFiles.add(file));
|
||||
const fileSuiteByFile = /* @__PURE__ */ new Map();
|
||||
const loaderHost = mode === "out-of-process" ? new import_loaderHost.OutOfProcessLoaderHost(config) : new import_loaderHost.InProcessLoaderHost(config);
|
||||
if (await loaderHost.start(errors)) {
|
||||
for (const file of allTestFiles) {
|
||||
const fileSuite = await loaderHost.loadTestFile(file, errors);
|
||||
fileSuiteByFile.set(file, fileSuite);
|
||||
errors.push(...createDuplicateTitlesErrors(config, fileSuite));
|
||||
}
|
||||
await loaderHost.stop();
|
||||
}
|
||||
for (const file of allTestFiles) {
|
||||
for (const dependency of (0, import_compilationCache.dependenciesForTestFile)(file)) {
|
||||
if (allTestFiles.has(dependency)) {
|
||||
const importer = import_path.default.relative(config.config.rootDir, file);
|
||||
const importee = import_path.default.relative(config.config.rootDir, dependency);
|
||||
errors.push({
|
||||
message: `Error: test file "${importer}" should not import test file "${importee}"`,
|
||||
location: { file, line: 1, column: 1 }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const [project, files] of testRun.projectFiles) {
|
||||
const suites = files.map((file) => fileSuiteByFile.get(file)).filter(Boolean);
|
||||
testRun.projectSuites.set(project, suites);
|
||||
}
|
||||
}
|
||||
async function createRootSuite(testRun, errors, shouldFilterOnly) {
|
||||
const config = testRun.config;
|
||||
const rootSuite = new import_test.Suite("", "root");
|
||||
const projectSuites = /* @__PURE__ */ new Map();
|
||||
const filteredProjectSuites = /* @__PURE__ */ new Map();
|
||||
{
|
||||
const cliFileFilters = (0, import_util.createFileFiltersFromArguments)(config.cliArgs);
|
||||
const grepMatcher = config.cliGrep ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrep)) : () => true;
|
||||
const grepInvertMatcher = config.cliGrepInvert ? (0, import_util.createTitleMatcher)((0, import_util.forceRegExp)(config.cliGrepInvert)) : () => false;
|
||||
const cliTitleMatcher = (title) => !grepInvertMatcher(title) && grepMatcher(title);
|
||||
for (const [project, fileSuites] of testRun.projectSuites) {
|
||||
const projectSuite = createProjectSuite(project, fileSuites);
|
||||
projectSuites.set(project, projectSuite);
|
||||
const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testFilters: config.preOnlyTestFilters });
|
||||
filteredProjectSuites.set(project, filteredProjectSuite);
|
||||
}
|
||||
}
|
||||
if (shouldFilterOnly) {
|
||||
const filteredRoot = new import_test.Suite("", "root");
|
||||
for (const filteredProjectSuite of filteredProjectSuites.values())
|
||||
filteredRoot._addSuite(filteredProjectSuite);
|
||||
(0, import_suiteUtils.filterOnly)(filteredRoot);
|
||||
for (const [project, filteredProjectSuite] of filteredProjectSuites) {
|
||||
if (!filteredRoot.suites.includes(filteredProjectSuite))
|
||||
filteredProjectSuites.delete(project);
|
||||
}
|
||||
}
|
||||
const projectClosure = (0, import_projectUtils.buildProjectsClosure)([...filteredProjectSuites.keys()], (project) => filteredProjectSuites.get(project)._hasTests());
|
||||
for (const [project, type] of projectClosure) {
|
||||
if (type === "top-level") {
|
||||
project.project.repeatEach = project.fullConfig.configCLIOverrides.repeatEach ?? project.project.repeatEach;
|
||||
rootSuite._addSuite(buildProjectSuite(project, filteredProjectSuites.get(project)));
|
||||
}
|
||||
}
|
||||
if (config.config.forbidOnly) {
|
||||
const onlyTestsAndSuites = rootSuite._getOnlyItems();
|
||||
if (onlyTestsAndSuites.length > 0) {
|
||||
const configFilePath = config.config.configFile ? import_path.default.relative(config.config.rootDir, config.config.configFile) : void 0;
|
||||
errors.push(...createForbidOnlyErrors(onlyTestsAndSuites, config.configCLIOverrides.forbidOnly, configFilePath));
|
||||
}
|
||||
}
|
||||
if (config.config.shard) {
|
||||
const testGroups = [];
|
||||
for (const projectSuite of rootSuite.suites) {
|
||||
for (const group of (0, import_testGroups.createTestGroups)(projectSuite, config.config.shard.total))
|
||||
testGroups.push(group);
|
||||
}
|
||||
const testGroupsInThisShard = (0, import_testGroups.filterForShard)(config.config.shard, config.configCLIOverrides.shardWeights, testGroups);
|
||||
const testsInThisShard = /* @__PURE__ */ new Set();
|
||||
for (const group of testGroupsInThisShard) {
|
||||
for (const test of group.tests)
|
||||
testsInThisShard.add(test);
|
||||
}
|
||||
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => testsInThisShard.has(test));
|
||||
}
|
||||
if (config.postShardTestFilters.length)
|
||||
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test)));
|
||||
const topLevelProjects = [];
|
||||
{
|
||||
const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject)));
|
||||
for (const [project, level] of projectClosure2.entries()) {
|
||||
if (level === "dependency")
|
||||
rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project)));
|
||||
else
|
||||
topLevelProjects.push(project);
|
||||
}
|
||||
}
|
||||
return { rootSuite, topLevelProjects };
|
||||
}
|
||||
function createProjectSuite(project, fileSuites) {
|
||||
const projectSuite = new import_test.Suite(project.project.name, "project");
|
||||
for (const fileSuite of fileSuites)
|
||||
projectSuite._addSuite((0, import_suiteUtils.bindFileSuiteToProject)(project, fileSuite));
|
||||
const grepMatcher = (0, import_util.createTitleMatcher)(project.project.grep);
|
||||
const grepInvertMatcher = project.project.grepInvert ? (0, import_util.createTitleMatcher)(project.project.grepInvert) : null;
|
||||
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(projectSuite, (test) => {
|
||||
const grepTitle = test._grepTitleWithTags();
|
||||
if (grepInvertMatcher?.(grepTitle))
|
||||
return false;
|
||||
return grepMatcher(grepTitle);
|
||||
});
|
||||
return projectSuite;
|
||||
}
|
||||
function filterProjectSuite(projectSuite, options) {
|
||||
if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testFilters.length)
|
||||
return projectSuite;
|
||||
const result = projectSuite._deepClone();
|
||||
if (options.cliFileFilters.length)
|
||||
(0, import_suiteUtils.filterByFocusedLine)(result, options.cliFileFilters);
|
||||
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => {
|
||||
if (!options.testFilters.every((filter) => filter(test)))
|
||||
return false;
|
||||
if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
function buildProjectSuite(project, projectSuite) {
|
||||
const result = new import_test.Suite(project.project.name, "project");
|
||||
result._fullProject = project;
|
||||
if (project.fullyParallel)
|
||||
result._parallelMode = "parallel";
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
result._addSuite(fileSuite);
|
||||
for (let repeatEachIndex = 1; repeatEachIndex < project.project.repeatEach; repeatEachIndex++) {
|
||||
const clone = fileSuite._deepClone();
|
||||
(0, import_suiteUtils.applyRepeatEachIndex)(project, clone, repeatEachIndex);
|
||||
result._addSuite(clone);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function createForbidOnlyErrors(onlyTestsAndSuites, forbidOnlyCLIFlag, configFilePath) {
|
||||
const errors = [];
|
||||
for (const testOrSuite of onlyTestsAndSuites) {
|
||||
const title = testOrSuite.titlePath().slice(2).join(" ");
|
||||
const configFilePathName = configFilePath ? `'${configFilePath}'` : "the Playwright configuration file";
|
||||
const forbidOnlySource = forbidOnlyCLIFlag ? `'--forbid-only' CLI flag` : `'forbidOnly' option in ${configFilePathName}`;
|
||||
const error = {
|
||||
message: `Error: item focused with '.only' is not allowed due to the ${forbidOnlySource}: "${title}"`,
|
||||
location: testOrSuite.location
|
||||
};
|
||||
errors.push(error);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
function createDuplicateTitlesErrors(config, fileSuite) {
|
||||
const errors = [];
|
||||
const testsByFullTitle = /* @__PURE__ */ new Map();
|
||||
for (const test of fileSuite.allTests()) {
|
||||
const fullTitle = test.titlePath().slice(1).join(" \u203A ");
|
||||
const existingTest = testsByFullTitle.get(fullTitle);
|
||||
if (existingTest) {
|
||||
const error = {
|
||||
message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.config.rootDir, existingTest)}`,
|
||||
location: test.location
|
||||
};
|
||||
errors.push(error);
|
||||
}
|
||||
testsByFullTitle.set(fullTitle, test);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
function buildItemLocation(rootDir, testOrSuite) {
|
||||
if (!testOrSuite.location)
|
||||
return "";
|
||||
return `${import_path.default.relative(rootDir, testOrSuite.location.file)}:${testOrSuite.location.line}`;
|
||||
}
|
||||
async function requireOrImportDefaultFunction(file, expectConstructor) {
|
||||
let func = await (0, import_transform.requireOrImport)(file);
|
||||
if (func && typeof func === "object" && "default" in func)
|
||||
func = func["default"];
|
||||
if (typeof func !== "function")
|
||||
throw (0, import_util.errorWithFile)(file, `file must export a single ${expectConstructor ? "class" : "function"}.`);
|
||||
return func;
|
||||
}
|
||||
function loadGlobalHook(config, file) {
|
||||
return requireOrImportDefaultFunction(import_path.default.resolve(config.config.rootDir, file), false);
|
||||
}
|
||||
function loadReporter(config, file) {
|
||||
return requireOrImportDefaultFunction(config ? import_path.default.resolve(config.config.rootDir, file) : file, true);
|
||||
}
|
||||
function sourceMapSources(file, cache) {
|
||||
let sources = [file];
|
||||
if (!file.endsWith(".js"))
|
||||
return sources;
|
||||
if (cache.has(file))
|
||||
return cache.get(file);
|
||||
try {
|
||||
const sourceMap = import_utilsBundle.sourceMapSupport.retrieveSourceMap(file);
|
||||
const sourceMapData = typeof sourceMap?.map === "string" ? JSON.parse(sourceMap.map) : sourceMap?.map;
|
||||
if (sourceMapData?.sources)
|
||||
sources = sourceMapData.sources.map((source) => import_path.default.resolve(import_path.default.dirname(file), source));
|
||||
} finally {
|
||||
cache.set(file, sources);
|
||||
return sources;
|
||||
}
|
||||
}
|
||||
async function loadTestList(config, filePath) {
|
||||
try {
|
||||
const content = await import_fs.default.promises.readFile(filePath, "utf-8");
|
||||
const lines = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
||||
const descriptions = lines.map((line) => {
|
||||
const delimiter = line.includes("\u203A") ? "\u203A" : ">";
|
||||
const tokens = line.split(delimiter).map((token) => token.trim());
|
||||
let project;
|
||||
if (tokens[0].startsWith("[")) {
|
||||
if (!tokens[0].endsWith("]"))
|
||||
throw new Error(`Malformed test description: ${line}`);
|
||||
project = tokens[0].substring(1, tokens[0].length - 1);
|
||||
tokens.shift();
|
||||
}
|
||||
return { project, file: (0, import_utils.toPosixPath)((0, import_util.parseLocationArg)(tokens[0]).file), titlePath: tokens.slice(1) };
|
||||
});
|
||||
const testFilter = (test) => descriptions.some((d) => {
|
||||
const [projectName, , ...titles] = test.titlePath();
|
||||
if (d.project !== void 0 && d.project !== projectName)
|
||||
return false;
|
||||
const relativeFile = (0, import_utils.toPosixPath)(import_path.default.relative(config.config.rootDir, test.location.file));
|
||||
if (relativeFile !== d.file)
|
||||
return false;
|
||||
return d.titlePath.length <= titles.length && d.titlePath.every((_, index) => titles[index] === d.titlePath[index]);
|
||||
});
|
||||
const fileFilter = (file) => {
|
||||
const relativeFile = (0, import_utils.toPosixPath)(import_path.default.relative(config.config.rootDir, file));
|
||||
return descriptions.some((d) => d.file === relativeFile);
|
||||
};
|
||||
return { testFilter, fileFilter };
|
||||
} catch (e) {
|
||||
throw (0, import_util.errorWithFile)(filePath, "Cannot read test list file: " + e.message);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
collectProjectsAndTestFiles,
|
||||
createRootSuite,
|
||||
loadFileSuites,
|
||||
loadGlobalHook,
|
||||
loadReporter,
|
||||
loadTestList
|
||||
});
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var loaderHost_exports = {};
|
||||
__export(loaderHost_exports, {
|
||||
InProcessLoaderHost: () => InProcessLoaderHost,
|
||||
OutOfProcessLoaderHost: () => OutOfProcessLoaderHost
|
||||
});
|
||||
module.exports = __toCommonJS(loaderHost_exports);
|
||||
var import_processHost = require("./processHost");
|
||||
var import_esmLoaderHost = require("../common/esmLoaderHost");
|
||||
var import_ipc = require("../common/ipc");
|
||||
var import_poolBuilder = require("../common/poolBuilder");
|
||||
var import_test = require("../common/test");
|
||||
var import_testLoader = require("../common/testLoader");
|
||||
var import_compilationCache = require("../transform/compilationCache");
|
||||
class InProcessLoaderHost {
|
||||
constructor(config) {
|
||||
this._config = config;
|
||||
this._poolBuilder = import_poolBuilder.PoolBuilder.createForLoader();
|
||||
}
|
||||
async start(errors) {
|
||||
return true;
|
||||
}
|
||||
async loadTestFile(file, testErrors) {
|
||||
const result = await (0, import_testLoader.loadTestFile)(file, this._config, testErrors);
|
||||
this._poolBuilder.buildPools(result, testErrors);
|
||||
return result;
|
||||
}
|
||||
async stop() {
|
||||
await (0, import_esmLoaderHost.incorporateCompilationCache)();
|
||||
}
|
||||
}
|
||||
class OutOfProcessLoaderHost {
|
||||
constructor(config) {
|
||||
this._config = config;
|
||||
this._processHost = new import_processHost.ProcessHost(require.resolve("../loader/loaderMain.js"), "loader", {});
|
||||
}
|
||||
async start(errors) {
|
||||
const startError = await this._processHost.startRunner((0, import_ipc.serializeConfig)(this._config, false));
|
||||
if (startError) {
|
||||
errors.push({
|
||||
message: `Test loader process failed to start with code "${startError.code}" and signal "${startError.signal}"`
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
async loadTestFile(file, testErrors) {
|
||||
const result = await this._processHost.sendMessage({ method: "loadTestFile", params: { file } });
|
||||
testErrors.push(...result.testErrors);
|
||||
return import_test.Suite._deepParse(result.fileSuite);
|
||||
}
|
||||
async stop() {
|
||||
const result = await this._processHost.sendMessage({ method: "getCompilationCacheFromLoader" });
|
||||
(0, import_compilationCache.addToCompilationCache)(result);
|
||||
await this._processHost.stop();
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
InProcessLoaderHost,
|
||||
OutOfProcessLoaderHost
|
||||
});
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var processHost_exports = {};
|
||||
__export(processHost_exports, {
|
||||
ProcessHost: () => ProcessHost
|
||||
});
|
||||
module.exports = __toCommonJS(processHost_exports);
|
||||
var import_child_process = __toESM(require("child_process"));
|
||||
var import_events = require("events");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
class ProcessHost extends import_events.EventEmitter {
|
||||
constructor(runnerScript, processName, env) {
|
||||
super();
|
||||
this._didSendStop = false;
|
||||
this._processDidExit = false;
|
||||
this._didExitAndRanOnExit = false;
|
||||
this._lastMessageId = 0;
|
||||
this._callbacks = /* @__PURE__ */ new Map();
|
||||
this._producedEnv = {};
|
||||
this._requestHandlers = /* @__PURE__ */ new Map();
|
||||
this._runnerScript = runnerScript;
|
||||
this._processName = processName;
|
||||
this._extraEnv = env;
|
||||
}
|
||||
async startRunner(runnerParams, options = {}) {
|
||||
(0, import_utils.assert)(!this.process, "Internal error: starting the same process twice");
|
||||
this.process = import_child_process.default.fork(require.resolve("../common/process"), {
|
||||
// Note: we pass detached:false, so that workers are in the same process group.
|
||||
// This way Ctrl+C or a kill command can shutdown all workers in case they misbehave.
|
||||
// Otherwise user can end up with a bunch of workers stuck in a busy loop without self-destructing.
|
||||
detached: false,
|
||||
env: {
|
||||
...process.env,
|
||||
...this._extraEnv
|
||||
},
|
||||
stdio: [
|
||||
"ignore",
|
||||
options.onStdOut ? "pipe" : "inherit",
|
||||
options.onStdErr && !process.env.PW_RUNNER_DEBUG ? "pipe" : "inherit",
|
||||
"ipc"
|
||||
]
|
||||
});
|
||||
this.process.on("exit", async (code, signal) => {
|
||||
this._processDidExit = true;
|
||||
await this.onExit();
|
||||
this._didExitAndRanOnExit = true;
|
||||
this.emit("exit", { unexpectedly: !this._didSendStop, code, signal });
|
||||
});
|
||||
this.process.on("error", (e) => {
|
||||
});
|
||||
this.process.on("message", (message) => {
|
||||
if (import_utilsBundle.debug.enabled("pw:test:protocol"))
|
||||
(0, import_utilsBundle.debug)("pw:test:protocol")("\u25C0 RECV " + JSON.stringify(message));
|
||||
if (message.method === "__env_produced__") {
|
||||
const producedEnv = message.params;
|
||||
this._producedEnv = Object.fromEntries(producedEnv.map((e) => [e[0], e[1] ?? void 0]));
|
||||
} else if (message.method === "__dispatch__") {
|
||||
const { id, error: error2, method, params, result } = message.params;
|
||||
if (id && this._callbacks.has(id)) {
|
||||
const { resolve, reject } = this._callbacks.get(id);
|
||||
this._callbacks.delete(id);
|
||||
if (error2) {
|
||||
const errorObject = new Error(error2.message);
|
||||
errorObject.stack = error2.stack;
|
||||
reject(errorObject);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
} else {
|
||||
this.emit(method, params);
|
||||
}
|
||||
} else if (message.method === "__request__") {
|
||||
const { id, method, params } = message.params;
|
||||
const handler = this._requestHandlers.get(method);
|
||||
if (!handler) {
|
||||
this.send({ method: "__response__", params: { id, error: { message: "Unknown method" } } });
|
||||
} else {
|
||||
handler(params).then((result) => {
|
||||
this.send({ method: "__response__", params: { id, result } });
|
||||
}).catch((error2) => {
|
||||
this.send({ method: "__response__", params: { id, error: { message: error2.message } } });
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.emit(message.method, message.params);
|
||||
}
|
||||
});
|
||||
if (options.onStdOut)
|
||||
this.process.stdout?.on("data", options.onStdOut);
|
||||
if (options.onStdErr)
|
||||
this.process.stderr?.on("data", options.onStdErr);
|
||||
const error = await new Promise((resolve) => {
|
||||
this.process.once("exit", (code, signal) => resolve({ unexpectedly: true, code, signal }));
|
||||
this.once("ready", () => resolve(void 0));
|
||||
});
|
||||
if (error)
|
||||
return error;
|
||||
const processParams = {
|
||||
processName: this._processName,
|
||||
timeOrigin: (0, import_utils.timeOrigin)()
|
||||
};
|
||||
this.send({
|
||||
method: "__init__",
|
||||
params: {
|
||||
processParams,
|
||||
runnerScript: this._runnerScript,
|
||||
runnerParams
|
||||
}
|
||||
});
|
||||
}
|
||||
sendMessage(message) {
|
||||
const id = ++this._lastMessageId;
|
||||
this.send({
|
||||
method: "__dispatch__",
|
||||
params: { id, ...message }
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
this._callbacks.set(id, { resolve, reject });
|
||||
});
|
||||
}
|
||||
sendMessageNoReply(message) {
|
||||
this.sendMessage(message).catch(() => {
|
||||
});
|
||||
}
|
||||
async onExit() {
|
||||
}
|
||||
onRequest(method, handler) {
|
||||
this._requestHandlers.set(method, handler);
|
||||
}
|
||||
async stop() {
|
||||
if (!this._processDidExit && !this._didSendStop) {
|
||||
this.send({ method: "__stop__" });
|
||||
this._didSendStop = true;
|
||||
}
|
||||
if (!this._didExitAndRanOnExit)
|
||||
await new Promise((f) => this.once("exit", f));
|
||||
}
|
||||
didSendStop() {
|
||||
return this._didSendStop;
|
||||
}
|
||||
producedEnv() {
|
||||
return this._producedEnv;
|
||||
}
|
||||
send(message) {
|
||||
if (import_utilsBundle.debug.enabled("pw:test:protocol"))
|
||||
(0, import_utilsBundle.debug)("pw:test:protocol")("SEND \u25BA " + JSON.stringify(message));
|
||||
this.process?.send(message);
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
ProcessHost
|
||||
});
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var projectUtils_exports = {};
|
||||
__export(projectUtils_exports, {
|
||||
buildDependentProjects: () => buildDependentProjects,
|
||||
buildProjectsClosure: () => buildProjectsClosure,
|
||||
buildTeardownToSetupsMap: () => buildTeardownToSetupsMap,
|
||||
collectFilesForProject: () => collectFilesForProject,
|
||||
filterProjects: () => filterProjects,
|
||||
findTopLevelProjects: () => findTopLevelProjects
|
||||
});
|
||||
module.exports = __toCommonJS(projectUtils_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_util = require("util");
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_util2 = require("../util");
|
||||
const readFileAsync = (0, import_util.promisify)(import_fs.default.readFile);
|
||||
const readDirAsync = (0, import_util.promisify)(import_fs.default.readdir);
|
||||
function wildcardPatternToRegExp(pattern) {
|
||||
return new RegExp("^" + pattern.split("*").map(import_utils.escapeRegExp).join(".*") + "$", "ig");
|
||||
}
|
||||
function filterProjects(projects, projectNames) {
|
||||
if (!projectNames)
|
||||
return [...projects];
|
||||
const projectNamesToFind = /* @__PURE__ */ new Set();
|
||||
const unmatchedProjectNames = /* @__PURE__ */ new Map();
|
||||
const patterns = /* @__PURE__ */ new Set();
|
||||
for (const name of projectNames) {
|
||||
const lowerCaseName = name.toLocaleLowerCase();
|
||||
if (lowerCaseName.includes("*")) {
|
||||
patterns.add(wildcardPatternToRegExp(lowerCaseName));
|
||||
} else {
|
||||
projectNamesToFind.add(lowerCaseName);
|
||||
unmatchedProjectNames.set(lowerCaseName, name);
|
||||
}
|
||||
}
|
||||
const result = projects.filter((project) => {
|
||||
const lowerCaseName = project.project.name.toLocaleLowerCase();
|
||||
if (projectNamesToFind.has(lowerCaseName)) {
|
||||
unmatchedProjectNames.delete(lowerCaseName);
|
||||
return true;
|
||||
}
|
||||
for (const regex of patterns) {
|
||||
regex.lastIndex = 0;
|
||||
if (regex.test(lowerCaseName))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (unmatchedProjectNames.size) {
|
||||
const unknownProjectNames = Array.from(unmatchedProjectNames.values()).map((n) => `"${n}"`).join(", ");
|
||||
throw new Error(`Project(s) ${unknownProjectNames} not found. Available projects: ${projects.map((p) => `"${p.project.name}"`).join(", ")}`);
|
||||
}
|
||||
if (!result.length) {
|
||||
const allProjects = projects.map((p) => `"${p.project.name}"`).join(", ");
|
||||
throw new Error(`No projects matched. Available projects: ${allProjects}`);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function buildTeardownToSetupsMap(projects) {
|
||||
const result = /* @__PURE__ */ new Map();
|
||||
for (const project of projects) {
|
||||
if (project.teardown) {
|
||||
const setups = result.get(project.teardown) || [];
|
||||
setups.push(project);
|
||||
result.set(project.teardown, setups);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function buildProjectsClosure(projects, hasTests) {
|
||||
const result = /* @__PURE__ */ new Map();
|
||||
const visit = (depth, project) => {
|
||||
if (depth > 100) {
|
||||
const error = new Error("Circular dependency detected between projects.");
|
||||
error.stack = "";
|
||||
throw error;
|
||||
}
|
||||
if (depth === 0 && hasTests && !hasTests(project))
|
||||
return;
|
||||
if (result.get(project) !== "dependency")
|
||||
result.set(project, depth ? "dependency" : "top-level");
|
||||
for (const dep of project.deps)
|
||||
visit(depth + 1, dep);
|
||||
if (project.teardown)
|
||||
visit(depth + 1, project.teardown);
|
||||
};
|
||||
for (const p of projects)
|
||||
visit(0, p);
|
||||
return result;
|
||||
}
|
||||
function findTopLevelProjects(config) {
|
||||
const closure = buildProjectsClosure(config.projects);
|
||||
return [...closure].filter((entry) => entry[1] === "top-level").map((entry) => entry[0]);
|
||||
}
|
||||
function buildDependentProjects(forProjects, projects) {
|
||||
const reverseDeps = new Map(projects.map((p) => [p, []]));
|
||||
for (const project of projects) {
|
||||
for (const dep of project.deps)
|
||||
reverseDeps.get(dep).push(project);
|
||||
}
|
||||
const result = /* @__PURE__ */ new Set();
|
||||
const visit = (depth, project) => {
|
||||
if (depth > 100) {
|
||||
const error = new Error("Circular dependency detected between projects.");
|
||||
error.stack = "";
|
||||
throw error;
|
||||
}
|
||||
result.add(project);
|
||||
for (const reverseDep of reverseDeps.get(project))
|
||||
visit(depth + 1, reverseDep);
|
||||
if (project.teardown)
|
||||
visit(depth + 1, project.teardown);
|
||||
};
|
||||
for (const forProject of forProjects)
|
||||
visit(0, forProject);
|
||||
return result;
|
||||
}
|
||||
async function collectFilesForProject(project, fsCache = /* @__PURE__ */ new Map()) {
|
||||
const extensions = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts", ".cjs", ".cts", ".jsx", ".tsx", ".mjsx", ".mtsx", ".cjsx", ".ctsx"]);
|
||||
const testFileExtension = (file) => extensions.has(import_path.default.extname(file));
|
||||
const allFiles = await cachedCollectFiles(project.project.testDir, project.respectGitIgnore, fsCache);
|
||||
const testMatch = (0, import_util2.createFileMatcher)(project.project.testMatch);
|
||||
const testIgnore = (0, import_util2.createFileMatcher)(project.project.testIgnore);
|
||||
const testFiles = allFiles.filter((file) => {
|
||||
if (!testFileExtension(file))
|
||||
return false;
|
||||
const isTest = !testIgnore(file) && testMatch(file);
|
||||
if (!isTest)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
return testFiles;
|
||||
}
|
||||
async function cachedCollectFiles(testDir, respectGitIgnore, fsCache) {
|
||||
const key = testDir + ":" + respectGitIgnore;
|
||||
let result = fsCache.get(key);
|
||||
if (!result) {
|
||||
result = await collectFiles(testDir, respectGitIgnore);
|
||||
fsCache.set(key, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
async function collectFiles(testDir, respectGitIgnore) {
|
||||
if (!import_fs.default.existsSync(testDir))
|
||||
return [];
|
||||
if (!import_fs.default.statSync(testDir).isDirectory())
|
||||
return [];
|
||||
const checkIgnores = (entryPath, rules, isDirectory, parentStatus) => {
|
||||
let status = parentStatus;
|
||||
for (const rule of rules) {
|
||||
const ruleIncludes = rule.negate;
|
||||
if (status === "included" === ruleIncludes)
|
||||
continue;
|
||||
const relative = import_path.default.relative(rule.dir, entryPath);
|
||||
if (rule.match("/" + relative) || rule.match(relative)) {
|
||||
status = ruleIncludes ? "included" : "ignored";
|
||||
} else if (isDirectory && (rule.match("/" + relative + "/") || rule.match(relative + "/"))) {
|
||||
status = ruleIncludes ? "included" : "ignored";
|
||||
} else if (isDirectory && ruleIncludes && (rule.match("/" + relative, true) || rule.match(relative, true))) {
|
||||
status = "ignored-but-recurse";
|
||||
}
|
||||
}
|
||||
return status;
|
||||
};
|
||||
const files = [];
|
||||
const visit = async (dir, rules, status) => {
|
||||
const entries = await readDirAsync(dir, { withFileTypes: true });
|
||||
entries.sort((a, b) => a.name.localeCompare(b.name));
|
||||
if (respectGitIgnore) {
|
||||
const gitignore = entries.find((e) => e.isFile() && e.name === ".gitignore");
|
||||
if (gitignore) {
|
||||
const content = await readFileAsync(import_path.default.join(dir, gitignore.name), "utf8");
|
||||
const newRules = content.split(/\r?\n/).map((s) => {
|
||||
s = s.trim();
|
||||
if (!s)
|
||||
return;
|
||||
const rule = new import_utilsBundle.minimatch.Minimatch(s, { matchBase: true, dot: true, flipNegate: true });
|
||||
if (rule.comment)
|
||||
return;
|
||||
rule.dir = dir;
|
||||
return rule;
|
||||
}).filter((rule) => !!rule);
|
||||
rules = [...rules, ...newRules];
|
||||
}
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.name === "." || entry.name === "..")
|
||||
continue;
|
||||
if (entry.isFile() && entry.name === ".gitignore")
|
||||
continue;
|
||||
if (entry.isDirectory() && entry.name === "node_modules")
|
||||
continue;
|
||||
const entryPath = import_path.default.join(dir, entry.name);
|
||||
const entryStatus = checkIgnores(entryPath, rules, entry.isDirectory(), status);
|
||||
if (entry.isDirectory() && entryStatus !== "ignored")
|
||||
await visit(entryPath, rules, entryStatus);
|
||||
else if (entry.isFile() && entryStatus === "included")
|
||||
files.push(entryPath);
|
||||
}
|
||||
};
|
||||
await visit(testDir, [], "included");
|
||||
return files;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
buildDependentProjects,
|
||||
buildProjectsClosure,
|
||||
buildTeardownToSetupsMap,
|
||||
collectFilesForProject,
|
||||
filterProjects,
|
||||
findTopLevelProjects
|
||||
});
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var rebase_exports = {};
|
||||
__export(rebase_exports, {
|
||||
addSuggestedRebaseline: () => addSuggestedRebaseline,
|
||||
applySuggestedRebaselines: () => applySuggestedRebaselines,
|
||||
clearSuggestedRebaselines: () => clearSuggestedRebaselines
|
||||
});
|
||||
module.exports = __toCommonJS(rebase_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_path = __toESM(require("path"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_utils2 = require("playwright-core/lib/utils");
|
||||
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
|
||||
var import_projectUtils = require("./projectUtils");
|
||||
var import_babelBundle = require("../transform/babelBundle");
|
||||
const t = import_babelBundle.types;
|
||||
const suggestedRebaselines = new import_utils.MultiMap();
|
||||
function addSuggestedRebaseline(location, suggestedRebaseline) {
|
||||
suggestedRebaselines.set(location.file, { location, code: suggestedRebaseline });
|
||||
}
|
||||
function clearSuggestedRebaselines() {
|
||||
suggestedRebaselines.clear();
|
||||
}
|
||||
async function applySuggestedRebaselines(config, reporter) {
|
||||
if (config.config.updateSnapshots === "none")
|
||||
return;
|
||||
if (!suggestedRebaselines.size)
|
||||
return;
|
||||
const [project] = (0, import_projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
||||
if (!project)
|
||||
return;
|
||||
const patches = [];
|
||||
const files = [];
|
||||
const gitCache = /* @__PURE__ */ new Map();
|
||||
const patchFile = import_path.default.join(project.project.outputDir, "rebaselines.patch");
|
||||
for (const fileName of [...suggestedRebaselines.keys()].sort()) {
|
||||
const source = await import_fs.default.promises.readFile(fileName, "utf8");
|
||||
const lines = source.split("\n");
|
||||
const replacements = suggestedRebaselines.get(fileName);
|
||||
const fileNode = (0, import_babelBundle.babelParse)(source, fileName, true);
|
||||
const ranges = [];
|
||||
(0, import_babelBundle.traverse)(fileNode, {
|
||||
CallExpression: (path2) => {
|
||||
const node = path2.node;
|
||||
if (node.arguments.length < 1)
|
||||
return;
|
||||
if (!t.isMemberExpression(node.callee))
|
||||
return;
|
||||
const argument = node.arguments[0];
|
||||
if (!t.isStringLiteral(argument) && !t.isTemplateLiteral(argument))
|
||||
return;
|
||||
const prop = node.callee.property;
|
||||
if (!prop.loc || !argument.start || !argument.end)
|
||||
return;
|
||||
for (const replacement of replacements) {
|
||||
if (prop.loc.start.line !== replacement.location.line)
|
||||
continue;
|
||||
if (prop.loc.start.column + 1 !== replacement.location.column)
|
||||
continue;
|
||||
const indent = lines[prop.loc.start.line - 1].match(/^\s*/)[0];
|
||||
const newText = replacement.code.replace(/\{indent\}/g, indent);
|
||||
ranges.push({ start: argument.start, end: argument.end, oldText: source.substring(argument.start, argument.end), newText });
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
ranges.sort((a, b) => b.start - a.start);
|
||||
let result = source;
|
||||
for (const range of ranges)
|
||||
result = result.substring(0, range.start) + range.newText + result.substring(range.end);
|
||||
const relativeName = import_path.default.relative(process.cwd(), fileName);
|
||||
files.push(relativeName);
|
||||
if (config.config.updateSourceMethod === "overwrite") {
|
||||
await import_fs.default.promises.writeFile(fileName, result);
|
||||
} else if (config.config.updateSourceMethod === "3way") {
|
||||
await import_fs.default.promises.writeFile(fileName, applyPatchWithConflictMarkers(source, result));
|
||||
} else {
|
||||
const gitFolder = findGitRoot(import_path.default.dirname(fileName), gitCache);
|
||||
const relativeToGit = import_path.default.relative(gitFolder || process.cwd(), fileName);
|
||||
patches.push(createPatch(relativeToGit, source, result));
|
||||
}
|
||||
}
|
||||
const fileList = files.map((file) => " " + import_utils2.colors.dim(file)).join("\n");
|
||||
reporter.onStdErr(`
|
||||
New baselines created for:
|
||||
|
||||
${fileList}
|
||||
`);
|
||||
if (config.config.updateSourceMethod === "patch") {
|
||||
await import_fs.default.promises.mkdir(import_path.default.dirname(patchFile), { recursive: true });
|
||||
await import_fs.default.promises.writeFile(patchFile, patches.join("\n"));
|
||||
reporter.onStdErr(`
|
||||
` + import_utils2.colors.cyan("git apply " + import_path.default.relative(process.cwd(), patchFile)) + "\n");
|
||||
}
|
||||
}
|
||||
function createPatch(fileName, before, after) {
|
||||
const file = fileName.replace(/\\/g, "/");
|
||||
const text = import_utilsBundle.diff.createPatch(file, before, after, void 0, void 0, { context: 3 });
|
||||
return [
|
||||
"diff --git a/" + file + " b/" + file,
|
||||
"--- a/" + file,
|
||||
"+++ b/" + file,
|
||||
...text.split("\n").slice(4)
|
||||
].join("\n");
|
||||
}
|
||||
function findGitRoot(dir, cache) {
|
||||
const result = cache.get(dir);
|
||||
if (result !== void 0)
|
||||
return result;
|
||||
const gitPath = import_path.default.join(dir, ".git");
|
||||
if (import_fs.default.existsSync(gitPath) && import_fs.default.lstatSync(gitPath).isDirectory()) {
|
||||
cache.set(dir, dir);
|
||||
return dir;
|
||||
}
|
||||
const parentDir = import_path.default.dirname(dir);
|
||||
if (dir === parentDir) {
|
||||
cache.set(dir, null);
|
||||
return null;
|
||||
}
|
||||
const parentResult = findGitRoot(parentDir, cache);
|
||||
cache.set(dir, parentResult);
|
||||
return parentResult;
|
||||
}
|
||||
function applyPatchWithConflictMarkers(oldText, newText) {
|
||||
const diffResult = import_utilsBundle.diff.diffLines(oldText, newText);
|
||||
let result = "";
|
||||
let conflict = false;
|
||||
diffResult.forEach((part) => {
|
||||
if (part.added) {
|
||||
if (conflict) {
|
||||
result += part.value;
|
||||
result += ">>>>>>> SNAPSHOT\n";
|
||||
conflict = false;
|
||||
} else {
|
||||
result += "<<<<<<< HEAD\n";
|
||||
result += part.value;
|
||||
result += "=======\n";
|
||||
conflict = true;
|
||||
}
|
||||
} else if (part.removed) {
|
||||
result += "<<<<<<< HEAD\n";
|
||||
result += part.value;
|
||||
result += "=======\n";
|
||||
conflict = true;
|
||||
} else {
|
||||
if (conflict) {
|
||||
result += ">>>>>>> SNAPSHOT\n";
|
||||
conflict = false;
|
||||
}
|
||||
result += part.value;
|
||||
}
|
||||
});
|
||||
if (conflict)
|
||||
result += ">>>>>>> SNAPSHOT\n";
|
||||
return result;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
addSuggestedRebaseline,
|
||||
applySuggestedRebaselines,
|
||||
clearSuggestedRebaselines
|
||||
});
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var reporters_exports = {};
|
||||
__export(reporters_exports, {
|
||||
createErrorCollectingReporter: () => createErrorCollectingReporter,
|
||||
createReporterForTestServer: () => createReporterForTestServer,
|
||||
createReporters: () => createReporters
|
||||
});
|
||||
module.exports = __toCommonJS(reporters_exports);
|
||||
var import_fs = __toESM(require("fs"));
|
||||
var import_utils = require("playwright-core/lib/utils");
|
||||
var import_loadUtils = require("./loadUtils");
|
||||
var import_base = require("../reporters/base");
|
||||
var import_blob = require("../reporters/blob");
|
||||
var import_dot = __toESM(require("../reporters/dot"));
|
||||
var import_empty = __toESM(require("../reporters/empty"));
|
||||
var import_github = __toESM(require("../reporters/github"));
|
||||
var import_html = __toESM(require("../reporters/html"));
|
||||
var import_json = __toESM(require("../reporters/json"));
|
||||
var import_junit = __toESM(require("../reporters/junit"));
|
||||
var import_line = __toESM(require("../reporters/line"));
|
||||
var import_list = __toESM(require("../reporters/list"));
|
||||
var import_listModeReporter = __toESM(require("../reporters/listModeReporter"));
|
||||
var import_reporterV2 = require("../reporters/reporterV2");
|
||||
async function createReporters(config, mode, descriptions) {
|
||||
const defaultReporters = {
|
||||
blob: import_blob.BlobReporter,
|
||||
dot: mode === "list" ? import_listModeReporter.default : import_dot.default,
|
||||
line: mode === "list" ? import_listModeReporter.default : import_line.default,
|
||||
list: mode === "list" ? import_listModeReporter.default : import_list.default,
|
||||
github: import_github.default,
|
||||
json: import_json.default,
|
||||
junit: import_junit.default,
|
||||
null: import_empty.default,
|
||||
html: import_html.default
|
||||
};
|
||||
const reporters = [];
|
||||
descriptions ??= config.config.reporter;
|
||||
if (config.configCLIOverrides.additionalReporters)
|
||||
descriptions = [...descriptions, ...config.configCLIOverrides.additionalReporters];
|
||||
const runOptions = reporterOptions(config, mode);
|
||||
for (const r of descriptions) {
|
||||
const [name, arg] = r;
|
||||
const options = { ...runOptions, ...arg };
|
||||
if (name in defaultReporters) {
|
||||
reporters.push(new defaultReporters[name](options));
|
||||
} else {
|
||||
const reporterConstructor = await (0, import_loadUtils.loadReporter)(config, name);
|
||||
reporters.push((0, import_reporterV2.wrapReporterAsV2)(new reporterConstructor(options)));
|
||||
}
|
||||
}
|
||||
if (process.env.PW_TEST_REPORTER) {
|
||||
const reporterConstructor = await (0, import_loadUtils.loadReporter)(config, process.env.PW_TEST_REPORTER);
|
||||
reporters.push((0, import_reporterV2.wrapReporterAsV2)(new reporterConstructor(runOptions)));
|
||||
}
|
||||
const someReporterPrintsToStdio = reporters.some((r) => r.printsToStdio ? r.printsToStdio() : true);
|
||||
if (reporters.length && !someReporterPrintsToStdio) {
|
||||
if (mode === "list")
|
||||
reporters.unshift(new import_listModeReporter.default());
|
||||
else if (mode !== "merge")
|
||||
reporters.unshift(!process.env.CI ? new import_line.default() : new import_dot.default());
|
||||
}
|
||||
return reporters;
|
||||
}
|
||||
async function createReporterForTestServer(file, messageSink) {
|
||||
const reporterConstructor = await (0, import_loadUtils.loadReporter)(null, file);
|
||||
return (0, import_reporterV2.wrapReporterAsV2)(new reporterConstructor({
|
||||
_send: messageSink
|
||||
}));
|
||||
}
|
||||
function createErrorCollectingReporter(screen) {
|
||||
const errors = [];
|
||||
return {
|
||||
version: () => "v2",
|
||||
onError(error) {
|
||||
errors.push(error);
|
||||
screen.stderr?.write((0, import_base.formatError)(screen, error).message + "\n");
|
||||
},
|
||||
errors: () => errors
|
||||
};
|
||||
}
|
||||
function reporterOptions(config, mode) {
|
||||
return {
|
||||
configDir: config.configDir,
|
||||
_mode: mode,
|
||||
_commandHash: computeCommandHash(config)
|
||||
};
|
||||
}
|
||||
function computeCommandHash(config) {
|
||||
const parts = [];
|
||||
if (config.cliProjectFilter)
|
||||
parts.push(...config.cliProjectFilter);
|
||||
const command = {};
|
||||
if (config.cliArgs.length)
|
||||
command.cliArgs = config.cliArgs;
|
||||
if (config.cliGrep)
|
||||
command.cliGrep = config.cliGrep;
|
||||
if (config.cliGrepInvert)
|
||||
command.cliGrepInvert = config.cliGrepInvert;
|
||||
if (config.cliOnlyChanged)
|
||||
command.cliOnlyChanged = config.cliOnlyChanged;
|
||||
if (config.config.tags.length)
|
||||
command.tags = config.config.tags.join(" ");
|
||||
if (config.cliTestList)
|
||||
command.cliTestList = (0, import_utils.calculateSha1)(import_fs.default.readFileSync(config.cliTestList));
|
||||
if (config.cliTestListInvert)
|
||||
command.cliTestListInvert = (0, import_utils.calculateSha1)(import_fs.default.readFileSync(config.cliTestListInvert));
|
||||
if (Object.keys(command).length)
|
||||
parts.push((0, import_utils.calculateSha1)(JSON.stringify(command)).substring(0, 7));
|
||||
return parts.join("-");
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
createErrorCollectingReporter,
|
||||
createReporterForTestServer,
|
||||
createReporters
|
||||
});
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
var sigIntWatcher_exports = {};
|
||||
__export(sigIntWatcher_exports, {
|
||||
SigIntWatcher: () => SigIntWatcher
|
||||
});
|
||||
module.exports = __toCommonJS(sigIntWatcher_exports);
|
||||
class SigIntWatcher {
|
||||
constructor() {
|
||||
this._hadSignal = false;
|
||||
let sigintCallback;
|
||||
this._sigintPromise = new Promise((f) => sigintCallback = f);
|
||||
this._sigintHandler = () => {
|
||||
FixedNodeSIGINTHandler.off(this._sigintHandler);
|
||||
this._hadSignal = true;
|
||||
sigintCallback();
|
||||
};
|
||||
FixedNodeSIGINTHandler.on(this._sigintHandler);
|
||||
}
|
||||
promise() {
|
||||
return this._sigintPromise;
|
||||
}
|
||||
hadSignal() {
|
||||
return this._hadSignal;
|
||||
}
|
||||
disarm() {
|
||||
FixedNodeSIGINTHandler.off(this._sigintHandler);
|
||||
}
|
||||
}
|
||||
class FixedNodeSIGINTHandler {
|
||||
static {
|
||||
this._handlers = [];
|
||||
}
|
||||
static {
|
||||
this._ignoreNextSIGINTs = false;
|
||||
}
|
||||
static {
|
||||
this._handlerInstalled = false;
|
||||
}
|
||||
static {
|
||||
this._dispatch = () => {
|
||||
if (this._ignoreNextSIGINTs)
|
||||
return;
|
||||
this._ignoreNextSIGINTs = true;
|
||||
setTimeout(() => {
|
||||
this._ignoreNextSIGINTs = false;
|
||||
if (!this._handlers.length)
|
||||
this._uninstall();
|
||||
}, 1e3);
|
||||
for (const handler of this._handlers)
|
||||
handler();
|
||||
};
|
||||
}
|
||||
static _install() {
|
||||
if (!this._handlerInstalled) {
|
||||
this._handlerInstalled = true;
|
||||
process.on("SIGINT", this._dispatch);
|
||||
}
|
||||
}
|
||||
static _uninstall() {
|
||||
if (this._handlerInstalled) {
|
||||
this._handlerInstalled = false;
|
||||
process.off("SIGINT", this._dispatch);
|
||||
}
|
||||
}
|
||||
static on(handler) {
|
||||
this._handlers.push(handler);
|
||||
if (this._handlers.length === 1)
|
||||
this._install();
|
||||
}
|
||||
static off(handler) {
|
||||
this._handlers = this._handlers.filter((h) => h !== handler);
|
||||
if (!this._ignoreNextSIGINTs && !this._handlers.length)
|
||||
this._uninstall();
|
||||
}
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
SigIntWatcher
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user