playwright 1.56.0-alpha-2025-09-18 → 1.56.0-alpha-1758292576000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agents/generator.md +66 -27
- package/lib/agents/healer.md +3 -1
- package/lib/agents/planner.md +63 -11
- package/lib/mcp/browser/browserContextFactory.js +42 -0
- package/lib/mcp/browser/config.js +1 -0
- package/lib/mcp/browser/watchdog.js +2 -0
- package/lib/mcp/program.js +1 -1
- package/lib/mcp/sdk/http.js +6 -0
- package/lib/mcp/sdk/server.js +4 -1
- package/lib/mcp/test/testTools.js +17 -12
- package/lib/program.js +6 -6
- package/lib/reporters/html.js +43 -20
- package/lib/runner/dispatcher.js +1 -1
- package/lib/runner/failureTracker.js +5 -3
- package/lib/runner/loadUtils.js +4 -1
- package/lib/runner/projectUtils.js +8 -2
- package/lib/runner/tasks.js +7 -4
- package/lib/runner/testRunner.js +2 -2
- package/lib/runner/testServer.js +4 -1
- package/lib/transform/compilationCache.js +22 -5
- package/package.json +2 -2
- package/types/test.d.ts +1 -0
package/lib/agents/generator.md
CHANGED
|
@@ -27,57 +27,96 @@ tools:
|
|
|
27
27
|
- playwright-test/test_setup_page
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
|
|
30
|
+
You are a Playwright Test Generator, an expert in browser automation and end-to-end testing.
|
|
31
|
+
Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate
|
|
32
|
+
application behavior.
|
|
31
33
|
|
|
32
34
|
Your process is methodical and thorough:
|
|
33
35
|
|
|
34
|
-
1. **Scenario Analysis
|
|
36
|
+
1. **Scenario Analysis**
|
|
37
|
+
- Carefully analyze the test scenario provided, identifying all user actions,
|
|
38
|
+
expected outcomes and validation points
|
|
35
39
|
|
|
36
|
-
2. **Interactive Execution
|
|
37
|
-
- For each
|
|
40
|
+
2. **Interactive Execution**
|
|
41
|
+
- For each scenario, start with the `test_setup_page` tool to set up page for the scenario
|
|
38
42
|
- Use Playwright tools to manually execute each step of the scenario in real-time
|
|
39
43
|
- Verify that each action works as expected
|
|
40
44
|
- Identify the correct locators and interaction patterns
|
|
41
45
|
- Observe actual application behavior and responses
|
|
42
|
-
- Catch potential timing issues or dynamic content
|
|
43
46
|
- Validate that assertions will work correctly
|
|
44
47
|
|
|
45
|
-
3. **Test Code Generation
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
48
|
+
3. **Test Code Generation**
|
|
49
|
+
|
|
50
|
+
After successfully completing the manual execution, generate clean, maintainable
|
|
51
|
+
@playwright/test source code that follows following convention:
|
|
52
|
+
|
|
53
|
+
- One file per scenario, one test in a file
|
|
54
|
+
- File name must be fs-friendly scenario name
|
|
55
|
+
- Test must be placed in a describe matching the top-level test plan item
|
|
56
|
+
- Test title must match the scenario name
|
|
57
|
+
- Includes a comment with the step text before each step execution
|
|
58
|
+
|
|
59
|
+
<example-generation>
|
|
60
|
+
For following plan:
|
|
61
|
+
|
|
62
|
+
```markdown file=specs/plan.md
|
|
63
|
+
### 1. Adding New Todos
|
|
64
|
+
**Seed:** `tests/seed.spec.ts`
|
|
65
|
+
|
|
66
|
+
#### 1.1 Add Valid Todo
|
|
67
|
+
**Steps:**
|
|
68
|
+
1. Click in the "What needs to be done?" input field
|
|
69
|
+
|
|
70
|
+
#### 1.2 Add Multiple Todos
|
|
71
|
+
...
|
|
72
|
+
```
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
74
|
+
Following file is generated:
|
|
75
|
+
|
|
76
|
+
```ts file=add-valid-todo.spec.ts
|
|
77
|
+
// spec: specs/plan.md
|
|
78
|
+
// seed: tests/seed.spec.ts
|
|
79
|
+
|
|
80
|
+
test.describe('Adding New Todos', () => {
|
|
81
|
+
test('Add Valid Todo', async { page } => {
|
|
82
|
+
// 1. Click in the "What needs to be done?" input field
|
|
83
|
+
await page.click(...);
|
|
84
|
+
|
|
85
|
+
...
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
</example-generation>
|
|
90
|
+
|
|
91
|
+
4. **Best practices**:
|
|
92
|
+
- Each test has clear, descriptive assertions that validate the expected behavior
|
|
58
93
|
- Includes proper error handling and meaningful failure messages
|
|
59
94
|
- Uses Playwright best practices (page.waitForLoadState, expect.toBeVisible, etc.)
|
|
95
|
+
- Do not improvise, do not add directives that were not asked for
|
|
96
|
+
- Uses reliable locators (preferring data-testid, role-based, or text-based selectors over fragile CSS selectors)
|
|
97
|
+
- Uses local variables for locators that are used multiple times
|
|
98
|
+
- Uses explicit waits rather than arbitrary timeouts
|
|
99
|
+
- Never waits for networkidle or use other discouraged or deprecated apis
|
|
100
|
+
- Is self-contained and can run independently
|
|
60
101
|
- Is deterministic and not prone to flaky behavior
|
|
61
|
-
- Follows consistent naming conventions and code structure
|
|
62
|
-
|
|
63
|
-
5. **Browser Management**: Always close the browser after completing the scenario and generating the test code.
|
|
64
|
-
|
|
65
|
-
Your goal is to produce production-ready Playwright tests that provide reliable validation of application functionality while being maintainable and easy to understand.
|
|
66
|
-
Process all scenarios sequentially, do not run in parallel. Save tests in the tests/ folder.
|
|
67
102
|
|
|
68
103
|
<example>
|
|
69
104
|
Context: User wants to test a login flow on their web application.
|
|
70
|
-
user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then
|
|
105
|
+
user: 'I need a test that logs into my app at localhost:3000 with username admin@test.com and password 123456, then
|
|
106
|
+
verifies the dashboard page loads'
|
|
71
107
|
assistant: 'I'll use the playwright-test-generator agent to create and validate this login test for you'
|
|
72
108
|
<commentary>
|
|
73
|
-
The user needs a specific browser automation test created, which is exactly what the playwright-test-generator agent
|
|
109
|
+
The user needs a specific browser automation test created, which is exactly what the playwright-test-generator agent
|
|
110
|
+
is designed for.
|
|
74
111
|
</commentary>
|
|
75
112
|
</example>
|
|
76
113
|
<example>
|
|
77
114
|
Context: User has built a new checkout flow and wants to ensure it works correctly.
|
|
78
|
-
user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the
|
|
115
|
+
user: 'Can you create a test that adds items to cart, proceeds to checkout, fills in payment details, and confirms the
|
|
116
|
+
order?'
|
|
79
117
|
assistant: 'I'll use the playwright-test-generator agent to build a comprehensive checkout flow test'
|
|
80
118
|
<commentary>
|
|
81
|
-
This is a complex user journey that needs to be automated and tested, perfect for the playwright-test-generator
|
|
119
|
+
This is a complex user journey that needs to be automated and tested, perfect for the playwright-test-generator
|
|
120
|
+
agent.
|
|
82
121
|
</commentary>
|
|
83
122
|
</example>
|
package/lib/agents/healer.md
CHANGED
|
@@ -49,7 +49,9 @@ Key principles:
|
|
|
49
49
|
- If multiple errors exist, fix them one at a time and retest
|
|
50
50
|
- Provide clear explanations of what was broken and how you fixed it
|
|
51
51
|
- You will continue this process until the test runs successfully without any failures or errors.
|
|
52
|
-
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
|
|
52
|
+
- If the error persists and you have high level of confidence that the test is correct, mark this test as test.fixme()
|
|
53
|
+
so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead
|
|
54
|
+
of the expected behavior.
|
|
53
55
|
- Do not ask user questions, you are not interactive tool, do the most reasonable thing possible to pass the test.
|
|
54
56
|
- Never wait for networkidle or use other discouraged or deprecated apis
|
|
55
57
|
|
package/lib/agents/planner.md
CHANGED
|
@@ -28,46 +28,97 @@ tools:
|
|
|
28
28
|
- playwright-test/test_setup_page
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
|
|
31
|
+
You are an expert web test planner with extensive experience in quality assurance, user experience testing, and test
|
|
32
|
+
scenario design. Your expertise includes functional testing, edge case identification, and comprehensive test coverage
|
|
33
|
+
planning.
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
You will:
|
|
34
36
|
|
|
35
|
-
1. **Navigate and Explore
|
|
37
|
+
1. **Navigate and Explore**
|
|
36
38
|
- Invoke the `test_setup_page` tool once to set up page before using any other tools
|
|
37
|
-
- Explore the
|
|
39
|
+
- Explore the browser snapshot
|
|
40
|
+
- Do not take screenshots unless absolutely necessary
|
|
41
|
+
- Use browser_* tools to navigate and discover interface
|
|
38
42
|
- Thoroughly explore the interface, identifying all interactive elements, forms, navigation paths, and functionality
|
|
39
43
|
|
|
40
|
-
2. **Analyze User Flows
|
|
44
|
+
2. **Analyze User Flows**
|
|
45
|
+
- Map out the primary user journeys and identify critical paths through the application
|
|
46
|
+
- Consider different user types and their typical behaviors
|
|
41
47
|
|
|
42
|
-
3. **Design Comprehensive Scenarios
|
|
48
|
+
3. **Design Comprehensive Scenarios**
|
|
49
|
+
|
|
50
|
+
Create detailed test scenarios that cover:
|
|
43
51
|
- Happy path scenarios (normal user behavior)
|
|
44
52
|
- Edge cases and boundary conditions
|
|
45
53
|
- Error handling and validation
|
|
46
54
|
|
|
47
|
-
4. **Structure Test Plans
|
|
55
|
+
4. **Structure Test Plans**
|
|
56
|
+
|
|
57
|
+
Each scenario must include:
|
|
48
58
|
- Clear, descriptive title
|
|
49
59
|
- Detailed step-by-step instructions
|
|
50
60
|
- Expected outcomes where appropriate
|
|
51
61
|
- Assumptions about starting state (always assume blank/fresh state)
|
|
52
62
|
- Success criteria and failure conditions
|
|
53
63
|
|
|
54
|
-
5. **Create Documentation
|
|
64
|
+
5. **Create Documentation**
|
|
65
|
+
|
|
66
|
+
Save your test plan as requested:
|
|
55
67
|
- Executive summary of the tested page/application
|
|
56
68
|
- Individual scenarios as separate sections
|
|
57
69
|
- Each scenario formatted with numbered steps
|
|
58
70
|
- Clear expected results for verification
|
|
59
71
|
|
|
72
|
+
<example-spec>
|
|
73
|
+
# TodoMVC Application - Comprehensive Test Plan
|
|
74
|
+
|
|
75
|
+
## Application Overview
|
|
76
|
+
|
|
77
|
+
The TodoMVC application is a React-based todo list manager that provides core task management functionality. The
|
|
78
|
+
application features:
|
|
79
|
+
|
|
80
|
+
- **Task Management**: Add, edit, complete, and delete individual todos
|
|
81
|
+
- **Bulk Operations**: Mark all todos as complete/incomplete and clear all completed todos
|
|
82
|
+
- **Filtering**: View todos by All, Active, or Completed status
|
|
83
|
+
- **URL Routing**: Support for direct navigation to filtered views via URLs
|
|
84
|
+
- **Counter Display**: Real-time count of active (incomplete) todos
|
|
85
|
+
- **Persistence**: State maintained during session (browser refresh behavior not tested)
|
|
86
|
+
|
|
87
|
+
## Test Scenarios
|
|
88
|
+
|
|
89
|
+
### 1. Adding New Todos
|
|
90
|
+
|
|
91
|
+
**Seed:** `tests/seed.spec.ts`
|
|
92
|
+
|
|
93
|
+
#### 1.1 Add Valid Todo
|
|
94
|
+
**Steps:**
|
|
95
|
+
1. Click in the "What needs to be done?" input field
|
|
96
|
+
2. Type "Buy groceries"
|
|
97
|
+
3. Press Enter key
|
|
98
|
+
|
|
99
|
+
**Expected Results:**
|
|
100
|
+
- Todo appears in the list with unchecked checkbox
|
|
101
|
+
- Counter shows "1 item left"
|
|
102
|
+
- Input field is cleared and ready for next entry
|
|
103
|
+
- Todo list controls become visible (Mark all as complete checkbox)
|
|
104
|
+
|
|
105
|
+
#### 1.2
|
|
106
|
+
...
|
|
107
|
+
</example-spec>
|
|
108
|
+
|
|
60
109
|
**Quality Standards**:
|
|
61
110
|
- Write steps that are specific enough for any tester to follow
|
|
62
111
|
- Include negative testing scenarios
|
|
63
112
|
- Ensure scenarios are independent and can be run in any order
|
|
64
113
|
|
|
65
|
-
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
|
|
114
|
+
**Output Format**: Always save the complete test plan as a markdown file with clear headings, numbered steps, and
|
|
115
|
+
professional formatting suitable for sharing with development and QA teams.
|
|
66
116
|
|
|
67
117
|
<example>
|
|
68
118
|
Context: User wants to test a new e-commerce checkout flow.
|
|
69
119
|
user: 'I need test scenarios for our new checkout process at https://mystore.com/checkout'
|
|
70
|
-
assistant: 'I'll use the playwright-test-planner agent to navigate to your checkout page and create comprehensive test
|
|
120
|
+
assistant: 'I'll use the playwright-test-planner agent to navigate to your checkout page and create comprehensive test
|
|
121
|
+
scenarios.'
|
|
71
122
|
<commentary>
|
|
72
123
|
The user needs test planning for a specific web page, so use the playwright-test-planner agent to explore and create
|
|
73
124
|
test scenarios.
|
|
@@ -76,7 +127,8 @@ When given a target web page or application, you will:
|
|
|
76
127
|
<example>
|
|
77
128
|
Context: User has deployed a new feature and wants thorough testing coverage.
|
|
78
129
|
user: 'Can you help me test our new user dashboard at https://app.example.com/dashboard?'
|
|
79
|
-
assistant: 'I'll launch the playwright-test-planner agent to explore your dashboard and develop detailed test
|
|
130
|
+
assistant: 'I'll launch the playwright-test-planner agent to explore your dashboard and develop detailed test
|
|
131
|
+
scenarios.'
|
|
80
132
|
<commentary>
|
|
81
133
|
This requires web exploration and test scenario creation, perfect for the playwright-test-planner agent.
|
|
82
134
|
</commentary>
|
|
@@ -28,6 +28,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
28
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
29
|
var browserContextFactory_exports = {};
|
|
30
30
|
__export(browserContextFactory_exports, {
|
|
31
|
+
SharedContextFactory: () => SharedContextFactory,
|
|
31
32
|
contextFactory: () => contextFactory
|
|
32
33
|
});
|
|
33
34
|
module.exports = __toCommonJS(browserContextFactory_exports);
|
|
@@ -42,6 +43,8 @@ var import_log = require("../log");
|
|
|
42
43
|
var import_config = require("./config");
|
|
43
44
|
var import_server2 = require("../sdk/server");
|
|
44
45
|
function contextFactory(config) {
|
|
46
|
+
if (config.sharedBrowserContext)
|
|
47
|
+
return SharedContextFactory.create(config);
|
|
45
48
|
if (config.browser.remoteEndpoint)
|
|
46
49
|
return new RemoteContextFactory(config);
|
|
47
50
|
if (config.browser.cdpEndpoint)
|
|
@@ -231,7 +234,46 @@ async function startTraceServer(config, tracesDir) {
|
|
|
231
234
|
function createHash(data) {
|
|
232
235
|
return import_crypto.default.createHash("sha256").update(data).digest("hex").slice(0, 7);
|
|
233
236
|
}
|
|
237
|
+
class SharedContextFactory {
|
|
238
|
+
static create(config) {
|
|
239
|
+
if (SharedContextFactory._instance)
|
|
240
|
+
throw new Error("SharedContextFactory already exists");
|
|
241
|
+
const baseConfig = { ...config, sharedBrowserContext: false };
|
|
242
|
+
const baseFactory = contextFactory(baseConfig);
|
|
243
|
+
SharedContextFactory._instance = new SharedContextFactory(baseFactory);
|
|
244
|
+
return SharedContextFactory._instance;
|
|
245
|
+
}
|
|
246
|
+
constructor(baseFactory) {
|
|
247
|
+
this._baseFactory = baseFactory;
|
|
248
|
+
}
|
|
249
|
+
async createContext(clientInfo, abortSignal, toolName) {
|
|
250
|
+
if (!this._contextPromise) {
|
|
251
|
+
(0, import_log.testDebug)("create shared browser context");
|
|
252
|
+
this._contextPromise = this._baseFactory.createContext(clientInfo, abortSignal, toolName);
|
|
253
|
+
}
|
|
254
|
+
const { browserContext } = await this._contextPromise;
|
|
255
|
+
(0, import_log.testDebug)(`shared context client connected`);
|
|
256
|
+
return {
|
|
257
|
+
browserContext,
|
|
258
|
+
close: async () => {
|
|
259
|
+
(0, import_log.testDebug)(`shared context client disconnected`);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
static async dispose() {
|
|
264
|
+
await SharedContextFactory._instance?._dispose();
|
|
265
|
+
}
|
|
266
|
+
async _dispose() {
|
|
267
|
+
const contextPromise = this._contextPromise;
|
|
268
|
+
this._contextPromise = void 0;
|
|
269
|
+
if (!contextPromise)
|
|
270
|
+
return;
|
|
271
|
+
const { close } = await contextPromise;
|
|
272
|
+
await close();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
234
275
|
// Annotate the CommonJS export names for ESM import in node:
|
|
235
276
|
0 && (module.exports = {
|
|
277
|
+
SharedContextFactory,
|
|
236
278
|
contextFactory
|
|
237
279
|
});
|
|
@@ -164,6 +164,7 @@ function configFromCLIOptions(cliOptions) {
|
|
|
164
164
|
saveSession: cliOptions.saveSession,
|
|
165
165
|
saveTrace: cliOptions.saveTrace,
|
|
166
166
|
secrets: cliOptions.secrets,
|
|
167
|
+
sharedBrowserContext: cliOptions.sharedBrowserContext,
|
|
167
168
|
outputDir: cliOptions.outputDir,
|
|
168
169
|
imageResponses: cliOptions.imageResponses,
|
|
169
170
|
timeouts: {
|
|
@@ -21,6 +21,7 @@ __export(watchdog_exports, {
|
|
|
21
21
|
setupExitWatchdog: () => setupExitWatchdog
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(watchdog_exports);
|
|
24
|
+
var import_browserContextFactory = require("./browserContextFactory");
|
|
24
25
|
var import_context = require("./context");
|
|
25
26
|
function setupExitWatchdog() {
|
|
26
27
|
let isExiting = false;
|
|
@@ -30,6 +31,7 @@ function setupExitWatchdog() {
|
|
|
30
31
|
isExiting = true;
|
|
31
32
|
setTimeout(() => process.exit(0), 15e3);
|
|
32
33
|
await import_context.Context.disposeAll();
|
|
34
|
+
await import_browserContextFactory.SharedContextFactory.dispose();
|
|
33
35
|
process.exit(0);
|
|
34
36
|
};
|
|
35
37
|
process.stdin.on("close", handleExit);
|
package/lib/mcp/program.js
CHANGED
|
@@ -41,7 +41,7 @@ var import_browserServerBackend = require("./browser/browserServerBackend");
|
|
|
41
41
|
var import_extensionContextFactory = require("./extension/extensionContextFactory");
|
|
42
42
|
var import_host = require("./vscode/host");
|
|
43
43
|
function decorateCommand(command, version) {
|
|
44
|
-
command.option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vscode", "VS Code tools.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
44
|
+
command.option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", import_config.semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", import_config.semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", import_config.commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--cdp-header <headers...>", "CDP headers to send with the connect request, multiple can be specified.", import_config.headerParser).option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').option("--grant-permissions <permissions...>", 'List of permissions to grant to the browser context, for example "geolocation", "clipboard-read", "clipboard-write".', import_config.commaSeparatedList).option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--secrets <path>", "path to a file containing secrets in the dotenv format", import_config.dotenvFileLoader).option("--shared-browser-context", "reuse the same browser context between all connected HTTP clients.").option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--timeout-action <timeout>", "specify action timeout in milliseconds, defaults to 5000ms", import_config.numberParser).option("--timeout-navigation <timeout>", "specify navigation timeout in milliseconds, defaults to 60000ms", import_config.numberParser).option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new import_utilsBundle.ProgramOption("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vscode", "VS Code tools.").hideHelp()).addOption(new import_utilsBundle.ProgramOption("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
45
45
|
(0, import_watchdog.setupExitWatchdog)();
|
|
46
46
|
if (options.vision) {
|
|
47
47
|
console.error("The --vision option is deprecated, use --caps=vision instead");
|
package/lib/mcp/sdk/http.js
CHANGED
|
@@ -72,6 +72,12 @@ async function installHttpTransport(httpServer, serverBackendFactory) {
|
|
|
72
72
|
const streamableSessions = /* @__PURE__ */ new Map();
|
|
73
73
|
httpServer.on("request", async (req, res) => {
|
|
74
74
|
const url = new URL(`http://localhost${req.url}`);
|
|
75
|
+
if (url.pathname === "/killkillkill" && req.method === "GET") {
|
|
76
|
+
res.statusCode = 200;
|
|
77
|
+
res.end("Killing process");
|
|
78
|
+
process.emit("SIGINT");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
75
81
|
if (url.pathname.startsWith("/sse"))
|
|
76
82
|
await handleSSE(serverBackendFactory, req, res, url, sseSessions);
|
|
77
83
|
else
|
package/lib/mcp/sdk/server.js
CHANGED
|
@@ -82,7 +82,10 @@ const initializeServer = async (server, backend, runHeartbeat) => {
|
|
|
82
82
|
const capabilities = server.getClientCapabilities();
|
|
83
83
|
let clientRoots = [];
|
|
84
84
|
if (capabilities?.roots) {
|
|
85
|
-
const { roots } = await server.listRoots()
|
|
85
|
+
const { roots } = await server.listRoots().catch((e) => {
|
|
86
|
+
serverDebug(e);
|
|
87
|
+
return { roots: [] };
|
|
88
|
+
});
|
|
86
89
|
clientRoots = roots;
|
|
87
90
|
}
|
|
88
91
|
const clientInfo = {
|
|
@@ -41,6 +41,7 @@ var import_bundle = require("../sdk/bundle");
|
|
|
41
41
|
var import_base = require("../../reporters/base");
|
|
42
42
|
var import_list = __toESM(require("../../reporters/list"));
|
|
43
43
|
var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
|
|
44
|
+
var import_projectUtils = require("../../runner/projectUtils");
|
|
44
45
|
var import_testTool = require("./testTool");
|
|
45
46
|
var import_streams = require("./streams");
|
|
46
47
|
const listTests = (0, import_testTool.defineTestTool)({
|
|
@@ -79,7 +80,8 @@ const runTests = (0, import_testTool.defineTestTool)({
|
|
|
79
80
|
const testRunner = await context.createTestRunner();
|
|
80
81
|
const result = await testRunner.runTests(reporter, {
|
|
81
82
|
locations: params.locations,
|
|
82
|
-
projects: params.projects
|
|
83
|
+
projects: params.projects,
|
|
84
|
+
disableConfigReporters: true
|
|
83
85
|
});
|
|
84
86
|
const text = stream.content();
|
|
85
87
|
return {
|
|
@@ -114,7 +116,8 @@ const debugTest = (0, import_testTool.defineTestTool)({
|
|
|
114
116
|
// For automatic recovery
|
|
115
117
|
timeout: 0,
|
|
116
118
|
workers: 1,
|
|
117
|
-
pauseOnError: true
|
|
119
|
+
pauseOnError: true,
|
|
120
|
+
disableConfigReporters: true
|
|
118
121
|
});
|
|
119
122
|
const text = stream.content();
|
|
120
123
|
return {
|
|
@@ -132,7 +135,7 @@ const setupPage = (0, import_testTool.defineTestTool)({
|
|
|
132
135
|
description: "Setup the page for test",
|
|
133
136
|
inputSchema: import_bundle.z.object({
|
|
134
137
|
project: import_bundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
|
|
135
|
-
testLocation: import_bundle.z.string().optional().describe('Location of the test to use for setup. For example: "test/
|
|
138
|
+
testLocation: import_bundle.z.string().optional().describe('Location of the seed test to use for setup. For example: "test/seed/default.spec.ts:20".')
|
|
136
139
|
}),
|
|
137
140
|
type: "readOnly"
|
|
138
141
|
},
|
|
@@ -143,16 +146,17 @@ const setupPage = (0, import_testTool.defineTestTool)({
|
|
|
143
146
|
const testRunner = await context.createTestRunner();
|
|
144
147
|
let testLocation = params.testLocation;
|
|
145
148
|
if (!testLocation) {
|
|
146
|
-
testLocation = ".
|
|
149
|
+
testLocation = "default.seed.spec.ts";
|
|
147
150
|
const config = await testRunner.loadConfig();
|
|
148
|
-
const project = params.project ? config.projects.find((p) => p.project.name === params.project) : config
|
|
151
|
+
const project = params.project ? config.projects.find((p) => p.project.name === params.project) : (0, import_projectUtils.findTopLevelProjects)(config)[0];
|
|
149
152
|
const testDir = project?.project.testDir || configDir;
|
|
150
|
-
const
|
|
151
|
-
if (!import_fs.default.existsSync(
|
|
152
|
-
await import_fs.default.promises.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
const seedFile = import_path.default.join(testDir, testLocation);
|
|
154
|
+
if (!import_fs.default.existsSync(seedFile)) {
|
|
155
|
+
await import_fs.default.promises.mkdir(import_path.default.dirname(seedFile), { recursive: true });
|
|
156
|
+
await import_fs.default.promises.writeFile(seedFile, `import { test, expect } from '@playwright/test';
|
|
157
|
+
|
|
158
|
+
test('seed', async ({ page }) => {});
|
|
159
|
+
`);
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
const result = await testRunner.runTests(reporter, {
|
|
@@ -161,7 +165,8 @@ const setupPage = (0, import_testTool.defineTestTool)({
|
|
|
161
165
|
projects: params.project ? [params.project] : void 0,
|
|
162
166
|
timeout: 0,
|
|
163
167
|
workers: 1,
|
|
164
|
-
pauseAtEnd: true
|
|
168
|
+
pauseAtEnd: true,
|
|
169
|
+
disableConfigReporters: true
|
|
165
170
|
});
|
|
166
171
|
const text = stream.content();
|
|
167
172
|
return {
|
package/lib/program.js
CHANGED
|
@@ -173,15 +173,15 @@ function addTestMCPServerCommand(program3) {
|
|
|
173
173
|
function addInitAgentsCommand(program3) {
|
|
174
174
|
const command = program3.command("init-agents", { hidden: true });
|
|
175
175
|
command.description("Initialize repository agents for the Claude Code");
|
|
176
|
-
command.
|
|
177
|
-
|
|
178
|
-
command.option
|
|
176
|
+
const option = command.createOption("--loop <loop>", "Agentic loop provider");
|
|
177
|
+
option.choices(["claude", "opencode", "vscode"]);
|
|
178
|
+
command.addOption(option);
|
|
179
179
|
command.action(async (opts) => {
|
|
180
|
-
if (opts.opencode)
|
|
180
|
+
if (opts.loop === "opencode")
|
|
181
181
|
await (0, import_generateAgents.initOpencodeRepo)();
|
|
182
|
-
else if (opts.vscode)
|
|
182
|
+
else if (opts.loop === "vscode")
|
|
183
183
|
await (0, import_generateAgents.initVSCodeRepo)();
|
|
184
|
-
else
|
|
184
|
+
else if (opts.loop === "claude")
|
|
185
185
|
await (0, import_generateAgents.initClaudeCodeRepo)();
|
|
186
186
|
});
|
|
187
187
|
}
|
package/lib/reporters/html.js
CHANGED
|
@@ -119,10 +119,17 @@ class HtmlReporter {
|
|
|
119
119
|
else if (process.env.PLAYWRIGHT_HTML_NO_COPY_PROMPT)
|
|
120
120
|
noCopyPrompt = true;
|
|
121
121
|
noCopyPrompt = noCopyPrompt || this._options.noCopyPrompt;
|
|
122
|
+
let noFiles;
|
|
123
|
+
if (process.env.PLAYWRIGHT_HTML_NO_FILES === "false" || process.env.PLAYWRIGHT_HTML_NO_FILES === "0")
|
|
124
|
+
noFiles = false;
|
|
125
|
+
else if (process.env.PLAYWRIGHT_HTML_NO_FILES)
|
|
126
|
+
noFiles = true;
|
|
127
|
+
noFiles = noFiles || this._options.noFiles;
|
|
122
128
|
const builder = new HtmlBuilder(this.config, this._outputFolder, this._attachmentsBaseURL, {
|
|
123
129
|
title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title,
|
|
124
130
|
noSnippets,
|
|
125
|
-
noCopyPrompt
|
|
131
|
+
noCopyPrompt,
|
|
132
|
+
noFiles
|
|
126
133
|
});
|
|
127
134
|
this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors);
|
|
128
135
|
}
|
|
@@ -220,23 +227,21 @@ class HtmlBuilder {
|
|
|
220
227
|
async build(metadata, projectSuites, result, topLevelErrors) {
|
|
221
228
|
const data = /* @__PURE__ */ new Map();
|
|
222
229
|
for (const projectSuite of projectSuites) {
|
|
230
|
+
const projectName = projectSuite.project().name;
|
|
223
231
|
for (const fileSuite of projectSuite.suites) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
for (const test of testEntries) {
|
|
238
|
-
testFile.tests.push(test.testCase);
|
|
239
|
-
testFileSummary.tests.push(test.testCaseSummary);
|
|
232
|
+
if (this._options.noFiles) {
|
|
233
|
+
for (const describeSuite of fileSuite.suites) {
|
|
234
|
+
const groupName = describeSuite.title;
|
|
235
|
+
this._createEntryForSuite(data, projectName, describeSuite, groupName, true);
|
|
236
|
+
}
|
|
237
|
+
const hasTestsOutsideGroups = fileSuite.tests.length > 0;
|
|
238
|
+
if (hasTestsOutsideGroups) {
|
|
239
|
+
const fileName = "<anonymous>";
|
|
240
|
+
this._createEntryForSuite(data, projectName, fileSuite, fileName, false);
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
const fileName = this._relativeLocation(fileSuite.location).file;
|
|
244
|
+
this._createEntryForSuite(data, projectName, fileSuite, fileName, true);
|
|
240
245
|
}
|
|
241
246
|
}
|
|
242
247
|
}
|
|
@@ -340,13 +345,31 @@ class HtmlBuilder {
|
|
|
340
345
|
_addDataFile(fileName, data) {
|
|
341
346
|
this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName);
|
|
342
347
|
}
|
|
343
|
-
|
|
348
|
+
_createEntryForSuite(data, projectName, suite, fileName, deep) {
|
|
349
|
+
const fileId = (0, import_utils.calculateSha1)(fileName).slice(0, 20);
|
|
350
|
+
let fileEntry = data.get(fileId);
|
|
351
|
+
if (!fileEntry) {
|
|
352
|
+
fileEntry = {
|
|
353
|
+
testFile: { fileId, fileName, tests: [] },
|
|
354
|
+
testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() }
|
|
355
|
+
};
|
|
356
|
+
data.set(fileId, fileEntry);
|
|
357
|
+
}
|
|
358
|
+
const { testFile, testFileSummary } = fileEntry;
|
|
359
|
+
const testEntries = [];
|
|
360
|
+
this._processSuite(suite, projectName, [], deep, testEntries);
|
|
361
|
+
for (const test of testEntries) {
|
|
362
|
+
testFile.tests.push(test.testCase);
|
|
363
|
+
testFileSummary.tests.push(test.testCaseSummary);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
_processSuite(suite, projectName, path2, deep, outTests) {
|
|
344
367
|
const newPath = [...path2, suite.title];
|
|
345
368
|
suite.entries().forEach((e) => {
|
|
346
369
|
if (e.type === "test")
|
|
347
370
|
outTests.push(this._createTestEntry(e, projectName, newPath));
|
|
348
|
-
else
|
|
349
|
-
this._processSuite(e, projectName, newPath, outTests);
|
|
371
|
+
else if (deep)
|
|
372
|
+
this._processSuite(e, projectName, newPath, deep, outTests);
|
|
350
373
|
});
|
|
351
374
|
}
|
|
352
375
|
_createTestEntry(test, projectName, path2) {
|
package/lib/runner/dispatcher.js
CHANGED
|
@@ -164,7 +164,7 @@ class Dispatcher {
|
|
|
164
164
|
extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {},
|
|
165
165
|
outputDir,
|
|
166
166
|
pauseOnError: this._failureTracker.pauseOnError(),
|
|
167
|
-
pauseAtEnd: this._failureTracker.pauseAtEnd()
|
|
167
|
+
pauseAtEnd: this._failureTracker.pauseAtEnd(projectConfig)
|
|
168
168
|
});
|
|
169
169
|
const handleOutput = (params) => {
|
|
170
170
|
const chunk = chunkFromParams(params);
|
|
@@ -26,11 +26,13 @@ class FailureTracker {
|
|
|
26
26
|
this._config = _config;
|
|
27
27
|
this._failureCount = 0;
|
|
28
28
|
this._hasWorkerErrors = false;
|
|
29
|
+
this._topLevelProjects = [];
|
|
29
30
|
this._pauseOnError = options?.pauseOnError ?? false;
|
|
30
31
|
this._pauseAtEnd = options?.pauseAtEnd ?? false;
|
|
31
32
|
}
|
|
32
|
-
onRootSuite(rootSuite) {
|
|
33
|
+
onRootSuite(rootSuite, topLevelProjects) {
|
|
33
34
|
this._rootSuite = rootSuite;
|
|
35
|
+
this._topLevelProjects = topLevelProjects;
|
|
34
36
|
}
|
|
35
37
|
onTestEnd(test, result) {
|
|
36
38
|
if (test.outcome() === "unexpected" && test.results.length > test.retries)
|
|
@@ -42,8 +44,8 @@ class FailureTracker {
|
|
|
42
44
|
pauseOnError() {
|
|
43
45
|
return this._pauseOnError;
|
|
44
46
|
}
|
|
45
|
-
pauseAtEnd() {
|
|
46
|
-
return this._pauseAtEnd;
|
|
47
|
+
pauseAtEnd(inProject) {
|
|
48
|
+
return this._pauseAtEnd && this._topLevelProjects.includes(inProject);
|
|
47
49
|
}
|
|
48
50
|
hasReachedMaxFailures() {
|
|
49
51
|
return this.maxFailures() > 0 && this._failureCount >= this.maxFailures();
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -168,14 +168,17 @@ async function createRootSuite(testRun, errors, shouldFilterOnly) {
|
|
|
168
168
|
}
|
|
169
169
|
if (config.postShardTestFilters.length)
|
|
170
170
|
(0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test)));
|
|
171
|
+
const topLevelProjects = [];
|
|
171
172
|
{
|
|
172
173
|
const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject)));
|
|
173
174
|
for (const [project, level] of projectClosure2.entries()) {
|
|
174
175
|
if (level === "dependency")
|
|
175
176
|
rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project)));
|
|
177
|
+
else
|
|
178
|
+
topLevelProjects.push(project);
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
|
-
return rootSuite;
|
|
181
|
+
return { rootSuite, topLevelProjects };
|
|
179
182
|
}
|
|
180
183
|
function createProjectSuite(project, fileSuites) {
|
|
181
184
|
const projectSuite = new import_test.Suite(project.project.name, "project");
|
|
@@ -32,7 +32,8 @@ __export(projectUtils_exports, {
|
|
|
32
32
|
buildProjectsClosure: () => buildProjectsClosure,
|
|
33
33
|
buildTeardownToSetupsMap: () => buildTeardownToSetupsMap,
|
|
34
34
|
collectFilesForProject: () => collectFilesForProject,
|
|
35
|
-
filterProjects: () => filterProjects
|
|
35
|
+
filterProjects: () => filterProjects,
|
|
36
|
+
findTopLevelProjects: () => findTopLevelProjects
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(projectUtils_exports);
|
|
38
39
|
var import_fs = __toESM(require("fs"));
|
|
@@ -116,6 +117,10 @@ function buildProjectsClosure(projects, hasTests) {
|
|
|
116
117
|
visit(0, p);
|
|
117
118
|
return result;
|
|
118
119
|
}
|
|
120
|
+
function findTopLevelProjects(config) {
|
|
121
|
+
const closure = buildProjectsClosure(config.projects);
|
|
122
|
+
return [...closure].filter((entry) => entry[1] === "top-level").map((entry) => entry[0]);
|
|
123
|
+
}
|
|
119
124
|
function buildDependentProjects(forProjects, projects) {
|
|
120
125
|
const reverseDeps = new Map(projects.map((p) => [p, []]));
|
|
121
126
|
for (const project of projects) {
|
|
@@ -231,5 +236,6 @@ async function collectFiles(testDir, respectGitIgnore) {
|
|
|
231
236
|
buildProjectsClosure,
|
|
232
237
|
buildTeardownToSetupsMap,
|
|
233
238
|
collectFilesForProject,
|
|
234
|
-
filterProjects
|
|
239
|
+
filterProjects,
|
|
240
|
+
findTopLevelProjects
|
|
235
241
|
});
|
package/lib/runner/tasks.js
CHANGED
|
@@ -65,6 +65,7 @@ class TestRun {
|
|
|
65
65
|
this.phases = [];
|
|
66
66
|
this.projectFiles = /* @__PURE__ */ new Map();
|
|
67
67
|
this.projectSuites = /* @__PURE__ */ new Map();
|
|
68
|
+
this.topLevelProjects = [];
|
|
68
69
|
this.config = config;
|
|
69
70
|
this.reporter = reporter;
|
|
70
71
|
this.failureTracker = new import_failureTracker.FailureTracker(config, options);
|
|
@@ -214,8 +215,9 @@ function createListFilesTask() {
|
|
|
214
215
|
return {
|
|
215
216
|
title: "load tests",
|
|
216
217
|
setup: async (testRun, errors) => {
|
|
217
|
-
|
|
218
|
-
testRun.
|
|
218
|
+
const { rootSuite, topLevelProjects } = await (0, import_loadUtils.createRootSuite)(testRun, errors, false);
|
|
219
|
+
testRun.rootSuite = rootSuite;
|
|
220
|
+
testRun.failureTracker.onRootSuite(rootSuite, topLevelProjects);
|
|
219
221
|
await (0, import_loadUtils.collectProjectsAndTestFiles)(testRun, false);
|
|
220
222
|
for (const [project, files] of testRun.projectFiles) {
|
|
221
223
|
const projectSuite = new import_test.Suite(project.project.name, "project");
|
|
@@ -247,8 +249,9 @@ function createLoadTask(mode, options) {
|
|
|
247
249
|
const changedFiles = await (0, import_vcs.detectChangedTestFiles)(testRun.config.cliOnlyChanged, testRun.config.configDir);
|
|
248
250
|
testRun.config.preOnlyTestFilters.push((test) => changedFiles.has(test.location.file));
|
|
249
251
|
}
|
|
250
|
-
|
|
251
|
-
testRun.
|
|
252
|
+
const { rootSuite, topLevelProjects } = await (0, import_loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly);
|
|
253
|
+
testRun.rootSuite = rootSuite;
|
|
254
|
+
testRun.failureTracker.onRootSuite(rootSuite, topLevelProjects);
|
|
252
255
|
if (options.failOnLoadErrors && !testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard && !testRun.config.cliOnlyChanged) {
|
|
253
256
|
if (testRun.config.cliArgs.length) {
|
|
254
257
|
throw new Error([
|
package/lib/runner/testRunner.js
CHANGED
|
@@ -269,12 +269,12 @@ class TestRunner extends import_events.default {
|
|
|
269
269
|
const testIdSet = new Set(params.testIds);
|
|
270
270
|
config.preOnlyTestFilters.push((test) => testIdSet.has(test.id));
|
|
271
271
|
}
|
|
272
|
-
const configReporters = await (0, import_reporters.createReporters)(config, "test", true);
|
|
272
|
+
const configReporters = params.disableConfigReporters ? [] : await (0, import_reporters.createReporters)(config, "test", true);
|
|
273
273
|
const reporter = new import_internalReporter.InternalReporter([...configReporters, userReporter]);
|
|
274
274
|
const stop = new import_utils.ManualPromise();
|
|
275
275
|
const tasks = [
|
|
276
276
|
(0, import_tasks.createApplyRebaselinesTask)(),
|
|
277
|
-
(0, import_tasks.createLoadTask)("out-of-process", { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter:
|
|
277
|
+
(0, import_tasks.createLoadTask)("out-of-process", { filterOnly: true, failOnLoadErrors: false, doNotRunDepsOutsideProjectFilter: params.doNotRunDepsOutsideProjectFilter }),
|
|
278
278
|
...(0, import_tasks.createRunTestsTasks)(config)
|
|
279
279
|
];
|
|
280
280
|
const testRun = new import_tasks.TestRun(config, reporter, { pauseOnError: params.pauseOnError, pauseAtEnd: params.pauseAtEnd });
|
package/lib/runner/testServer.js
CHANGED
|
@@ -149,7 +149,10 @@ class TestServerDispatcher {
|
|
|
149
149
|
}
|
|
150
150
|
async runTests(params) {
|
|
151
151
|
const wireReporter = await this._wireReporter((e) => this._dispatchEvent("report", e));
|
|
152
|
-
const { status } = await this._testRunner.runTests(wireReporter,
|
|
152
|
+
const { status } = await this._testRunner.runTests(wireReporter, {
|
|
153
|
+
...params,
|
|
154
|
+
doNotRunDepsOutsideProjectFilter: true
|
|
155
|
+
});
|
|
153
156
|
return { status };
|
|
154
157
|
}
|
|
155
158
|
async watch(params) {
|
|
@@ -49,6 +49,7 @@ module.exports = __toCommonJS(compilationCache_exports);
|
|
|
49
49
|
var import_fs = __toESM(require("fs"));
|
|
50
50
|
var import_os = __toESM(require("os"));
|
|
51
51
|
var import_path = __toESM(require("path"));
|
|
52
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
52
53
|
var import_globals = require("../common/globals");
|
|
53
54
|
var import_utilsBundle = require("../utilsBundle");
|
|
54
55
|
const cacheDir = process.env.PWTEST_CACHE_DIR || (() => {
|
|
@@ -90,7 +91,7 @@ function _innerAddToCompilationCacheAndSerialize(filename, entry) {
|
|
|
90
91
|
externalDependencies: []
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
|
-
function getFromCompilationCache(filename,
|
|
94
|
+
function getFromCompilationCache(filename, contentHash, moduleUrl) {
|
|
94
95
|
const cache = memoryCache.get(filename);
|
|
95
96
|
if (cache?.codePath) {
|
|
96
97
|
try {
|
|
@@ -98,7 +99,10 @@ function getFromCompilationCache(filename, hash, moduleUrl) {
|
|
|
98
99
|
} catch {
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
const
|
|
102
|
+
const filePathHash = calculateFilePathHash(filename);
|
|
103
|
+
const hashPrefix = filePathHash + "_" + contentHash.substring(0, 7);
|
|
104
|
+
const cacheFolderName = filePathHash.substring(0, 2);
|
|
105
|
+
const cachePath = calculateCachePath(filename, cacheFolderName, hashPrefix);
|
|
102
106
|
const codePath = cachePath + ".js";
|
|
103
107
|
const sourceMapPath = cachePath + ".map";
|
|
104
108
|
const dataPath = cachePath + ".data";
|
|
@@ -112,6 +116,7 @@ function getFromCompilationCache(filename, hash, moduleUrl) {
|
|
|
112
116
|
addToCache: (code, map, data) => {
|
|
113
117
|
if ((0, import_globals.isWorkerProcess)())
|
|
114
118
|
return {};
|
|
119
|
+
clearOldCacheEntries(cacheFolderName, filePathHash);
|
|
115
120
|
import_fs.default.mkdirSync(import_path.default.dirname(cachePath), { recursive: true });
|
|
116
121
|
if (map)
|
|
117
122
|
import_fs.default.writeFileSync(sourceMapPath, JSON.stringify(map), "utf8");
|
|
@@ -145,9 +150,21 @@ function addToCompilationCache(payload) {
|
|
|
145
150
|
externalDependencies.set(entry[0], /* @__PURE__ */ new Set([...entry[1], ...existing]));
|
|
146
151
|
}
|
|
147
152
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
function calculateFilePathHash(filePath) {
|
|
154
|
+
return (0, import_utils.calculateSha1)(filePath).substring(0, 10);
|
|
155
|
+
}
|
|
156
|
+
function calculateCachePath(filePath, cacheFolderName, hashPrefix) {
|
|
157
|
+
const fileName = hashPrefix + "_" + import_path.default.basename(filePath, import_path.default.extname(filePath)).replace(/\W/g, "");
|
|
158
|
+
return import_path.default.join(cacheDir, cacheFolderName, fileName);
|
|
159
|
+
}
|
|
160
|
+
function clearOldCacheEntries(cacheFolderName, filePathHash) {
|
|
161
|
+
const cachePath = import_path.default.join(cacheDir, cacheFolderName);
|
|
162
|
+
try {
|
|
163
|
+
const cachedRelevantFiles = import_fs.default.readdirSync(cachePath).filter((file) => file.startsWith(filePathHash));
|
|
164
|
+
for (const file of cachedRelevantFiles)
|
|
165
|
+
import_fs.default.rmSync(import_path.default.join(cachePath, file), { force: true });
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
151
168
|
}
|
|
152
169
|
let depsCollector;
|
|
153
170
|
function startCollectingFileDeps() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "playwright",
|
|
3
|
-
"version": "1.56.0-alpha-
|
|
3
|
+
"version": "1.56.0-alpha-1758292576000",
|
|
4
4
|
"description": "A high-level API to automate web browsers",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
"license": "Apache-2.0",
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"playwright-core": "1.56.0-alpha-
|
|
67
|
+
"playwright-core": "1.56.0-alpha-1758292576000"
|
|
68
68
|
},
|
|
69
69
|
"optionalDependencies": {
|
|
70
70
|
"fsevents": "2.3.2"
|