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.
@@ -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. Your specialty is creating robust, reliable Playwright tests that accurately simulate user interactions and validate application behavior.
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**: Carefully analyze the test scenario provided, identifying all user actions, expected outcomes, and validation points. Break down complex flows into discrete, testable steps.
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 test, start with the `test_setup_page` tool to set up page for the scenario
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**: After successfully completing the manual execution, generate clean, maintainable @playwright/test source code that:
46
- - Uses descriptive test names that clearly indicate what is being tested
47
- - Implements proper page object patterns when beneficial
48
- - Includes appropriate waits and assertions
49
- - Handles dynamic content and loading states
50
- - Uses reliable locators (preferring data-testid, role-based, or text-based selectors over fragile CSS selectors)
51
- - Includes proper setup and teardown
52
- - Is self-contained and can run independently
53
- - Use explicit waits rather than arbitrary timeouts
54
- - Never wait for networkidle or use other discouraged or deprecated apis
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
- 4. **Quality Assurance**: Ensure each generated test:
57
- - Has clear, descriptive assertions that validate the expected behavior
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 verifies the dashboard page loads'
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 is designed for.
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 order?'
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 agent.
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>
@@ -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() so that it is skipped during the execution. Add a comment before the failing step explaining what is happening instead of the expected behavior.
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
 
@@ -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 scenario design. Your expertise includes functional testing, usability testing, edge case identification, and comprehensive test coverage planning.
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
- When given a target web page or application, you will:
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 aria snapshot, use browser_* tools to navigate and discover interface.
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**: Map out the primary user journeys and identify critical paths through the application. Consider different user types and their typical behaviors.
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**: Create detailed test scenarios that cover:
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**: Each scenario must include:
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**: Save your test plan as a markdown file in specs/ folder with:
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 professional formatting suitable for sharing with development and QA teams.
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 scenarios.'
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 scenarios.'
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);
@@ -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");
@@ -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
@@ -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/e2e/file.spec.ts:20". Sets up blank page if no location is provided.')
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 = ".template.spec.ts";
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.projects[0];
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 templateFile = import_path.default.join(testDir, testLocation);
151
- if (!import_fs.default.existsSync(templateFile)) {
152
- await import_fs.default.promises.writeFile(templateFile, `
153
- import { test, expect } from '@playwright/test';
154
- test('template', async ({ page }) => {});
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.option("--claude", "Initialize repository agents for the Claude Code");
177
- command.option("--opencode", "Initialize repository agents for the Opencode");
178
- command.option("--vscode", "Initialize repository agents for the VS Code Copilot");
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
  }
@@ -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
- const fileName = this._relativeLocation(fileSuite.location).file;
225
- const fileId = (0, import_utils.calculateSha1)((0, import_utils.toPosixPath)(fileName)).slice(0, 20);
226
- let fileEntry = data.get(fileId);
227
- if (!fileEntry) {
228
- fileEntry = {
229
- testFile: { fileId, fileName, tests: [] },
230
- testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() }
231
- };
232
- data.set(fileId, fileEntry);
233
- }
234
- const { testFile, testFileSummary } = fileEntry;
235
- const testEntries = [];
236
- this._processSuite(fileSuite, projectSuite.project().name, [], testEntries);
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
- _processSuite(suite, projectName, path2, outTests) {
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) {
@@ -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();
@@ -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
  });
@@ -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
- testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, errors, false);
218
- testRun.failureTracker.onRootSuite(testRun.rootSuite);
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
- testRun.rootSuite = await (0, import_loadUtils.createRootSuite)(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly);
251
- testRun.failureTracker.onRootSuite(testRun.rootSuite);
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([
@@ -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: true }),
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 });
@@ -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, params);
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, hash, moduleUrl) {
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 cachePath = calculateCachePath(filename, hash);
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 calculateCachePath(filePath, hash) {
149
- const fileName = import_path.default.basename(filePath, import_path.default.extname(filePath)).replace(/\W/g, "") + "_" + hash;
150
- return import_path.default.join(cacheDir, hash[0] + hash[1], fileName);
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-2025-09-18",
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-2025-09-18"
67
+ "playwright-core": "1.56.0-alpha-1758292576000"
68
68
  },
69
69
  "optionalDependencies": {
70
70
  "fsevents": "2.3.2"
package/types/test.d.ts CHANGED
@@ -31,6 +31,7 @@ export type HtmlReporterOptions = {
31
31
  title?: string;
32
32
  noSnippets?: boolean;
33
33
  noCopyPrompt?: boolean;
34
+ noFiles?: boolean;
34
35
  };
35
36
 
36
37
  export type ReporterDescription = Readonly<