diff --git a/types/test.d.ts b/types/test.d.ts index 6c45b5fda4..3a7352633b 100644 --- a/types/test.d.ts +++ b/types/test.d.ts @@ -1,3 +1,4 @@ +// This file is generated by /utils/generate_types/index.js /** * Copyright (c) Microsoft Corporation. * @@ -43,75 +44,435 @@ type ExpectSettings = { }; /** - * Test run configuration. + * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple + * configurations. For example, consider running tests against multiple browsers. + * + * `TestProject` encapsulates configuration specific to a single project. Projects are configured in + * [testConfig.projects](https://playwright.dev/docs/api/class-testconfig#test-config-projects) specified in the + * [configuration file](https://playwright.dev/docs/test-configuration). Note that all properties of [TestProject] are available in the top-level + * [TestConfig], in which case they are shared between all projects. + * + * Here is an example configuration that runs every test in Chromium, Firefox and WebKit, both Desktop and Mobile versions. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * const { devices } = require('@playwright/test'); + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * // Options shared for all projects. + * timeout: 30000, + * use: { + * ignoreHTTPSErrors: true, + * }, + * + * // Options specific to each project. + * projects: [ + * { + * name: 'Desktop Chromium', + * use: { + * browserName: 'chromium', + * viewport: { width: 1280, height: 720 }, + * }, + * }, + * { + * name: 'Desktop Safari', + * use: { + * browserName: 'webkit', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Desktop Firefox', + * use: { + * browserName: 'firefox', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Mobile Chrome', + * use: devices['Pixel 5'], + * }, + * { + * name: 'Mobile Safari', + * use: devices['iPhone 12'], + * }, + * ], + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig, devices } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * // Options shared for all projects. + * timeout: 30000, + * use: { + * ignoreHTTPSErrors: true, + * }, + * + * // Options specific to each project. + * projects: [ + * { + * name: 'Desktop Chromium', + * use: { + * browserName: 'chromium', + * viewport: { width: 1280, height: 720 }, + * }, + * }, + * { + * name: 'Desktop Safari', + * use: { + * browserName: 'webkit', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Desktop Firefox', + * use: { + * browserName: 'firefox', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Mobile Chrome', + * use: devices['Pixel 5'], + * }, + * { + * name: 'Mobile Safari', + * use: devices['iPhone 12'], + * }, + * ], + * }; + * export default config; + * ``` + * */ -interface ProjectBase { +interface TestProject { /** - * Expect matcher settings. + * Configuration for the `expect` assertion library. */ expect?: ExpectSettings; - /** * Any JSON-serializable metadata that will be put directly to the test report. */ metadata?: any; - /** - * The project name, shown in the title of each test. + * Project name is visible in the report and during test execution. */ name?: string; - /** - * Output directory for files created during the test run. + * The output directory for files created during test execution. Defaults to `test-results`. + * + * This directory is cleaned at the start. When running a test, a unique subdirectory inside the + * [testProject.outputDir](https://playwright.dev/docs/api/class-testproject#test-project-output-dir) is created, + * guaranteeing that test running in parallel do not conflict. This directory can be accessed by + * [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) and + * [testInfo.outputPath(pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-output-path). + * + * Here is an example that uses + * [testInfo.outputPath(pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-output-path) to create a + * temporary file. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * const fs = require('fs'); + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the file', 'utf8'); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * import fs from 'fs'; + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the file', 'utf8'); + * }); + * ``` + * */ outputDir?: string; - /** * The number of times to repeat each test, useful for debugging flaky tests. */ repeatEach?: number; - /** - * The maximum number of retry attempts given to failed tests. + * The maximum number of retry attempts given to failed tests. Learn more about [test retries](https://playwright.dev/docs/test-retries). */ retries?: number; - /** - * Directory that will be recursively scanned for test files. + * Directory that will be recursively scanned for test files. Defaults to the directory of the configuration file. + * + * Each project can use a different directory. Here is an example that runs smoke tests in three browsers and all other + * tests in stable Chrome browser. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * projects: [ + * { + * name: 'Smoke Chromium', + * testDir: './smoke-tests', + * use: { + * browserName: 'chromium', + * } + * }, + * { + * name: 'Smoke WebKit', + * testDir: './smoke-tests', + * use: { + * browserName: 'webkit', + * } + * }, + * { + * name: 'Smoke Firefox', + * testDir: './smoke-tests', + * use: { + * browserName: 'firefox', + * } + * }, + * { + * name: 'Chrome Stable', + * testDir: './', + * use: { + * browserName: 'chromium', + * channel: 'chrome', + * } + * }, + * ], + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * projects: [ + * { + * name: 'Smoke Chromium', + * testDir: './smoke-tests', + * use: { + * browserName: 'chromium', + * } + * }, + * { + * name: 'Smoke WebKit', + * testDir: './smoke-tests', + * use: { + * browserName: 'webkit', + * } + * }, + * { + * name: 'Smoke Firefox', + * testDir: './smoke-tests', + * use: { + * browserName: 'firefox', + * } + * }, + * { + * name: 'Chrome Stable', + * testDir: './', + * use: { + * browserName: 'chromium', + * channel: 'chrome', + * } + * }, + * ], + * }; + * export default config; + * ``` + * */ testDir?: string; - /** - * Files matching one of these patterns are not executed as test files. - * Matching is performed against the absolute file path. - * Strings are treated as glob patterns. + * Files matching one of these patterns are not executed as test files. Matching is performed against the absolute file + * path. Strings are treated as glob patterns. + * + * For example, `'**\/test-assets/**'` will ignore any files in the `test-assets` directory. */ testIgnore?: string | RegExp | (string | RegExp)[]; - /** - * Only the files matching one of these patterns are executed as test files. - * Matching is performed against the absolute file path. - * Strings are treated as glob patterns. + * Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute + * file path. Strings are treated as glob patterns. + * + * By default, Playwright Test looks for files matching `.*(test|spec)\.(js|ts|mjs)`. */ testMatch?: string | RegExp | (string | RegExp)[]; - /** - * Timeout for each test in milliseconds. + * Timeout for each test in milliseconds. Defaults to 30 seconds. + * + * This is a base timeout for all tests. In addition, each test can configure its own timeout with + * [test.setTimeout(timeout)](https://playwright.dev/docs/api/class-test#test-set-timeout). */ timeout?: number; } /** - * Test run configuration. + * Playwright Test supports running multiple test projects at the same time. This is useful for running tests in multiple + * configurations. For example, consider running tests against multiple browsers. + * + * `TestProject` encapsulates configuration specific to a single project. Projects are configured in + * [testConfig.projects](https://playwright.dev/docs/api/class-testconfig#test-config-projects) specified in the + * [configuration file](https://playwright.dev/docs/test-configuration). Note that all properties of [TestProject] are available in the top-level + * [TestConfig], in which case they are shared between all projects. + * + * Here is an example configuration that runs every test in Chromium, Firefox and WebKit, both Desktop and Mobile versions. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * const { devices } = require('@playwright/test'); + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * // Options shared for all projects. + * timeout: 30000, + * use: { + * ignoreHTTPSErrors: true, + * }, + * + * // Options specific to each project. + * projects: [ + * { + * name: 'Desktop Chromium', + * use: { + * browserName: 'chromium', + * viewport: { width: 1280, height: 720 }, + * }, + * }, + * { + * name: 'Desktop Safari', + * use: { + * browserName: 'webkit', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Desktop Firefox', + * use: { + * browserName: 'firefox', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Mobile Chrome', + * use: devices['Pixel 5'], + * }, + * { + * name: 'Mobile Safari', + * use: devices['iPhone 12'], + * }, + * ], + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig, devices } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * // Options shared for all projects. + * timeout: 30000, + * use: { + * ignoreHTTPSErrors: true, + * }, + * + * // Options specific to each project. + * projects: [ + * { + * name: 'Desktop Chromium', + * use: { + * browserName: 'chromium', + * viewport: { width: 1280, height: 720 }, + * }, + * }, + * { + * name: 'Desktop Safari', + * use: { + * browserName: 'webkit', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Desktop Firefox', + * use: { + * browserName: 'firefox', + * viewport: { width: 1280, height: 720 }, + * } + * }, + * { + * name: 'Mobile Chrome', + * use: devices['Pixel 5'], + * }, + * { + * name: 'Mobile Safari', + * use: devices['iPhone 12'], + * }, + * ], + * }; + * export default config; + * ``` + * */ -export interface Project extends ProjectBase { - /** - * Fixtures defined for abstract tests created with `test.declare()` method. - */ +export interface Project extends TestProject { define?: FixtureDefine | FixtureDefine[]; - /** - * Fixture overrides for this run. Useful for specifying options. + * Additional fixtures for this project. Most useful for specifying options, for example + * [fixtures.browserName](https://playwright.dev/docs/api/class-fixtures#fixtures-browser-name). Learn more about + * [Fixtures] and [configuration](https://playwright.dev/docs/test-configuration). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * projects: [ + * { + * name: 'Chromium', + * use: { + * browserName: 'chromium', + * }, + * }, + * ], + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * projects: [ + * { + * name: 'Chromium', + * use: { + * browserName: 'chromium', + * }, + * }, + * ], + * }; + * export default config; + * ``` + * */ use?: Fixtures<{}, {}, TestArgs, WorkerArgs>; } @@ -146,134 +507,549 @@ export type LaunchConfig = { cwd?: string, }; -type LiteralUnion = T | (U & { zz_IGNORE_ME?: never }) - +type LiteralUnion = T | (U & { zz_IGNORE_ME?: never }); /** - * Testing configuration. + * Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or + * `testDir`. These options are described in the [TestConfig] object in the [configuration file](https://playwright.dev/docs/test-configuration). + * + * Playwright Test supports running multiple test projects at the same time. Project-specific options should be put to + * [testConfig.projects](https://playwright.dev/docs/api/class-testconfig#test-config-projects), but top-level [TestConfig] + * can also define base options shared between all projects. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * export default config; + * ``` + * */ -interface ConfigBase { +interface TestConfig { /** - * Whether to exit with an error if any tests are marked as `test.only`. Useful on CI. + * Whether to exit with an error if any tests or groups are marked as + * [test.only(title, testFunction)](https://playwright.dev/docs/api/class-test#test-only) or + * [test.describe.only(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-only). Useful on CI. */ forbidOnly?: boolean; - /** - * Path to the global setup file. This file will be required and run before all the tests. - * It must export a single function. + * Path to the global setup file. This file will be required and run before all the tests. It must export a single + * function. + * + * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * globalSetup: './global-setup', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig, devices } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * globalSetup: './global-setup', + * }; + * export default config; + * ``` + * */ globalSetup?: string; - /** - * Path to the global teardown file. This file will be required and run after all the tests. - * It must export a single function. + * Path to the global teardown file. This file will be required and run after all the tests. It must export a single + * function. See also [testConfig.globalSetup](https://playwright.dev/docs/api/class-testconfig#test-config-global-setup). + * + * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). */ globalTeardown?: string; - /** - * Maximum time in milliseconds the whole test suite can run. + * Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on CI + * to prevent broken setup from running too long and wasting resources. */ globalTimeout?: number; - /** - * Filter to only run tests with a title matching one of the patterns. + * Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run + * tests with "cart" in the title. Also available in the [command line](https://playwright.dev/docs/test-cli) with the `-g` option. + * + * `grep` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). */ grep?: RegExp | RegExp[]; - /** - * Filter out tests with a title matching one of the patterns. + * Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of + * [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep). Also available in the + * [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option. + * + * `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). */ grepInvert?: RegExp | RegExp[]; - - /** - * The maximum number of test failures for this test run. After reaching this number, - * testing will stop and exit with an error. Setting to zero (default) disables this behavior. + /** + * The maximum number of test failures for the whole test suite run. After reaching this number, testing will stop and exit + * with an error. Setting to zero (default) disables this behavior. + * + * Also available in the [command line](https://playwright.dev/docs/test-cli) with the `--max-failures` and `-x` options. */ maxFailures?: number; - /** - * Whether to preserve test output in the `outputDir`: + * Whether to preserve test output in the + * [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to `'always'`. * - `'always'` - preserve output for all tests; * - `'never'` - do not preserve output for any tests; * - `'failures-only'` - only preserve output for failed tests. */ preserveOutput?: PreserveOutput; - /** - * Whether to suppress stdio output from the tests. + * Playwright Test supports running multiple test projects at the same time. See [TestProject] for more information. */ - quiet?: boolean; - + projects?: Project[]; /** - * Reporter to use. Available options: - * - `'list'` - default reporter, prints a single line per test; - * - `'dot'` - minimal reporter that prints a single character per test run, useful on CI; - * - `'line'` - uses a single line for all successfull runs, useful for large test suites; - * - `'json'` - outputs a json file with information about the run; - * - `'junit'` - outputs an xml file with junit-alike information about the run; - * - `'null'` - no reporter, test run will be silent. + * Whether to suppress stdio and stderr output from the tests. + */ + quiet?: boolean; + /** + * The list of reporters to use. Each reporter can be: + * - A builtin reporter name like `'list'` or `'json'`. + * - A module name like `'my-awesome-reporter'`. + * - A relative path to the reporter like `'./reporters/my-awesome-reporter.js'`. + * + * You can pass options to the reporter in a tuple like `['json', { outputFile: './report.json' }]`. + * + * Learn more in the [reporters guide](https://playwright.dev/docs/test-reporters). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * reporter: 'line', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * reporter: 'line', + * }; + * export default config; + * ``` * - * It is possible to pass multiple reporters. A common pattern is using one terminal reporter - * like `'line'` or `'list'`, and one file reporter like `'json'` or `'junit'`. */ reporter?: LiteralUnion<'list'|'dot'|'line'|'json'|'junit'|'null', string> | ReporterDescription[]; - /** - * Whether to report slow tests. When `null`, slow tests are not reported. - * Otherwise, tests that took more than `threshold` milliseconds are reported as slow, - * but no more than `max` number of them. Passing zero as `max` reports all slow tests - * that exceed the threshold. + * Whether to report slow tests. Pass `null` to disable this feature. + * + * Tests that took more than `threshold` milliseconds are considered slow, and the slowest ones are reported, no more than + * `max` number of them. Passing zero as `max` reports all slow tests that exceed the threshold. */ reportSlowTests?: ReportSlowTests; - /** - * Shard tests and execute only the selected shard. - * Specify in the one-based form `{ total: 5, current: 2 }`. + * Shard tests and execute only the selected shard. Specify in the one-based form like `{ total: 5, current: 2 }`. + * + * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. */ shard?: Shard; - /** - * Whether to update expected snapshots with the actual results produced by the test run. + * Whether to update expected snapshots with the actual results produced by the test run. Defaults to `'missing'`. + * - `'all'` - All tests that are executed will update snapshots. + * - `'none'` - No snapshots are updated. + * - `'missing'` - Missing snapshots are created, for example when authoring a new test and running it for the first + * time. This is the default. + * + * Learn more about [snapshots](https://playwright.dev/docs/test-snapshots). */ updateSnapshots?: UpdateSnapshots; - - /** - * Launch a web server before running tests. - */ _launch?: LaunchConfig | LaunchConfig[]; - /** * The maximum number of concurrent worker processes to use for parallelizing tests. + * + * Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to + * speed up test execution. + * + * Defaults to one half of the number of CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with + * Playwright Test. */ workers?: number; + + /** + * Configuration for the `expect` assertion library. + */ + expect?: ExpectSettings; + /** + * Any JSON-serializable metadata that will be put directly to the test report. + */ + metadata?: any; + name?: string; + /** + * The output directory for files created during test execution. Defaults to `test-results`. + * + * This directory is cleaned at the start. When running a test, a unique subdirectory inside the + * [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir) is created, guaranteeing + * that test running in parallel do not conflict. This directory can be accessed by + * [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) and + * [testInfo.outputPath(pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-output-path). + * + * Here is an example that uses + * [testInfo.outputPath(pathSegments)](https://playwright.dev/docs/api/class-testinfo#test-info-output-path) to create a + * temporary file. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * const fs = require('fs'); + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the file', 'utf8'); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * import fs from 'fs'; + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the file', 'utf8'); + * }); + * ``` + * + */ + outputDir?: string; + /** + * The number of times to repeat each test, useful for debugging flaky tests. + */ + repeatEach?: number; + /** + * The maximum number of retry attempts given to failed tests. Learn more about [test retries](https://playwright.dev/docs/test-retries). + */ + retries?: number; + /** + * Directory that will be recursively scanned for test files. Defaults to the directory of the configuration file. + */ + testDir?: string; + /** + * Files matching one of these patterns are not executed as test files. Matching is performed against the absolute file + * path. Strings are treated as glob patterns. + * + * For example, `'**\/test-assets/**'` will ignore any files in the `test-assets` directory. + */ + testIgnore?: string | RegExp | (string | RegExp)[]; + /** + * Only the files matching one of these patterns are executed as test files. Matching is performed against the absolute + * file path. Strings are treated as glob patterns. + * + * By default, Playwright Test looks for files matching `.*(test|spec)\.(js|ts|mjs)`. + */ + testMatch?: string | RegExp | (string | RegExp)[]; + /** + * Timeout for each test in milliseconds. Defaults to 30 seconds. + * + * This is a base timeout for all tests. In addition, each test can configure its own timeout with + * [test.setTimeout(timeout)](https://playwright.dev/docs/api/class-test#test-set-timeout). + */ + timeout?: number; } /** - * Testing configuration. + * Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or + * `testDir`. These options are described in the [TestConfig] object in the [configuration file](https://playwright.dev/docs/test-configuration). + * + * Playwright Test supports running multiple test projects at the same time. Project-specific options should be put to + * [testConfig.projects](https://playwright.dev/docs/api/class-testconfig#test-config-projects), but top-level [TestConfig] + * can also define base options shared between all projects. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * export default config; + * ``` + * */ -export interface Config extends ConfigBase, Project { +export interface Config extends TestConfig { /** - * Projects specify test files that are executed with a specific configuration. + * Playwright Test supports running multiple test projects at the same time. See [TestProject] for more information. */ projects?: Project[]; + define?: FixtureDefine | FixtureDefine[]; + /** + * Additional fixtures for this project. Most useful for specifying options, for example + * [fixtures.browserName](https://playwright.dev/docs/api/class-fixtures#fixtures-browser-name). Learn more about + * [Fixtures] and [configuration](https://playwright.dev/docs/test-configuration). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * browserName: 'chromium', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * use: { + * browserName: 'chromium', + * }, + * }; + * export default config; + * ``` + * + */ + use?: Fixtures<{}, {}, TestArgs, WorkerArgs>; } +/** + * Playwright Test provides many options to configure how your tests are collected and executed, for example `timeout` or + * `testDir`. These options are described in the [TestConfig] object in the [configuration file](https://playwright.dev/docs/test-configuration). + * + * Playwright Test supports running multiple test projects at the same time. Project-specific options should be put to + * [testConfig.projects](https://playwright.dev/docs/api/class-testconfig#test-config-projects), but top-level [TestConfig] + * can also define base options shared between all projects. + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * timeout: 30000, + * globalTimeout: 600000, + * reporter: 'list', + * testDir: './tests', + * }; + * export default config; + * ``` + * + */ export interface FullConfig { + /** + * Whether to exit with an error if any tests or groups are marked as + * [test.only(title, testFunction)](https://playwright.dev/docs/api/class-test#test-only) or + * [test.describe.only(title, callback)](https://playwright.dev/docs/api/class-test#test-describe-only). Useful on CI. + */ forbidOnly: boolean; + /** + * Path to the global setup file. This file will be required and run before all the tests. It must export a single + * function. + * + * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * globalSetup: './global-setup', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig, devices } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * globalSetup: './global-setup', + * }; + * export default config; + * ``` + * + */ globalSetup: string | null; + /** + * Path to the global teardown file. This file will be required and run after all the tests. It must export a single + * function. See also [testConfig.globalSetup](https://playwright.dev/docs/api/class-testconfig#test-config-global-setup). + * + * Learn more about [global setup and teardown](https://playwright.dev/docs/test-advanced#global-setup-and-teardown). + */ globalTeardown: string | null; + /** + * Maximum time in milliseconds the whole test suite can run. Zero timeout (default) disables this behavior. Useful on CI + * to prevent broken setup from running too long and wasting resources. + */ globalTimeout: number; + /** + * Filter to only run tests with a title matching one of the patterns. For example, passing `grep: /cart/` should only run + * tests with "cart" in the title. Also available in the [command line](https://playwright.dev/docs/test-cli) with the `-g` option. + * + * `grep` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). + */ grep: RegExp | RegExp[]; + /** + * Filter to only run tests with a title **not** matching one of the patterns. This is the opposite of + * [testConfig.grep](https://playwright.dev/docs/api/class-testconfig#test-config-grep). Also available in the + * [command line](https://playwright.dev/docs/test-cli) with the `--grep-invert` option. + * + * `grepInvert` option is also useful for [tagging tests](https://playwright.dev/docs/test-annotations#tag-tests). + */ grepInvert: RegExp | RegExp[] | null; + /** + * The maximum number of test failures for the whole test suite run. After reaching this number, testing will stop and exit + * with an error. Setting to zero (default) disables this behavior. + * + * Also available in the [command line](https://playwright.dev/docs/test-cli) with the `--max-failures` and `-x` options. + */ maxFailures: number; + /** + * Whether to preserve test output in the + * [testConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir). Defaults to `'always'`. + * - `'always'` - preserve output for all tests; + * - `'never'` - do not preserve output for any tests; + * - `'failures-only'` - only preserve output for failed tests. + */ preserveOutput: PreserveOutput; + /** + * Playwright Test supports running multiple test projects at the same time. See [TestProject] for more information. + */ projects: FullProject[]; + /** + * The list of reporters to use. Each reporter can be: + * - A builtin reporter name like `'list'` or `'json'`. + * - A module name like `'my-awesome-reporter'`. + * - A relative path to the reporter like `'./reporters/my-awesome-reporter.js'`. + * + * You can pass options to the reporter in a tuple like `['json', { outputFile: './report.json' }]`. + * + * Learn more in the [reporters guide](https://playwright.dev/docs/test-reporters). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * reporter: 'line', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * reporter: 'line', + * }; + * export default config; + * ``` + * + */ reporter: ReporterDescription[]; + /** + * Whether to report slow tests. Pass `null` to disable this feature. + * + * Tests that took more than `threshold` milliseconds are considered slow, and the slowest ones are reported, no more than + * `max` number of them. Passing zero as `max` reports all slow tests that exceed the threshold. + */ reportSlowTests: ReportSlowTests; rootDir: string; + /** + * Whether to suppress stdio and stderr output from the tests. + */ quiet: boolean; + /** + * Shard tests and execute only the selected shard. Specify in the one-based form like `{ total: 5, current: 2 }`. + * + * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. + */ shard: Shard; + /** + * Whether to update expected snapshots with the actual results produced by the test run. Defaults to `'missing'`. + * - `'all'` - All tests that are executed will update snapshots. + * - `'none'` - No snapshots are updated. + * - `'missing'` - Missing snapshots are created, for example when authoring a new test and running it for the first + * time. This is the default. + * + * Learn more about [snapshots](https://playwright.dev/docs/test-snapshots). + */ updateSnapshots: UpdateSnapshots; + /** + * The maximum number of concurrent worker processes to use for parallelizing tests. + * + * Playwright Test uses worker processes to run tests. There is always at least one worker process, but more can be used to + * speed up test execution. + * + * Defaults to one half of the number of CPU cores. Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with + * Playwright Test. + */ workers: number; _launch: LaunchConfig[]; } @@ -281,661 +1057,1048 @@ export interface FullConfig { export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped'; /** - * Information about an error caught during test execution. + * Information about an error thrown during test execution. */ export interface TestError { /** - * Error message. Set when Error (or its subclass) has been thrown. + * Error message. Set when [Error] (or its subclass) has been thrown. */ message?: string; - /** - * Error stack. Set when Error (or its subclass) has been thrown. + * Error stack. Set when [Error] (or its subclass) has been thrown. */ stack?: string; - /** - * The thrown value. Set when anything except the Error (or its subclass) has been thrown. + * The value that was thrown. Set when anything except the [Error] (or its subclass) has been thrown. */ value?: string; } /** - * Information common for all tests run in the same worker process. + * `WorkerInfo` contains information about the worker that is running tests. It is available to + * [test.beforeAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-before-all) and + * [test.afterAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-all) hooks and worker-scoped + * fixtures. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeAll(async ({ browserName }, workerInfo) => { + * console.log(`Running ${browserName} in worker #${workerInfo.workerIndex}`); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeAll(async ({ browserName }, workerInfo) => { + * console.log(`Running ${browserName} in worker #${workerInfo.workerIndex}`); + * }); + * ``` + * */ export interface WorkerInfo { /** - * Testing configuration. + * Processed configuration from the [configuration file](https://playwright.dev/docs/test-configuration). */ config: FullConfig; - /** - * Specific project configuration for this worker. - * Different projects are always run in separate processes. + * Processed project configuration from the [configuration file](https://playwright.dev/docs/test-configuration). */ project: FullProject; - /** - * Unique worker index. Also available as `process.env.TEST_WORKER_INDEX`. + * The unique index of the worker process that is running the test. Also available as `process.env.TEST_WORKER_INDEX`. + * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. */ workerIndex: number; } /** - * Information about a particular test run. + * `TestInfo` contains information about currently running test. It is available to any test function, + * [test.beforeEach(hookFunction)](https://playwright.dev/docs/api/class-test#test-before-each) and + * [test.afterEach(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-each) hooks and test-scoped + * fixtures. `TestInfo` provides utilities to control test execution: attach files, update test timeout, determine which + * test is currently running and whether it was retried, etc. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }, testInfo) => { + * expect(testInfo.title).toBe('basic test'); + * await page.screenshot(testInfo.outputPath('screenshot.png')); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }, testInfo) => { + * expect(testInfo.title).toBe('basic test'); + * await page.screenshot(testInfo.outputPath('screenshot.png')); + * }); + * ``` + * */ -export interface TestInfo extends WorkerInfo { +export interface TestInfo { /** - * Test title as passed to `test('my test title', testFunction)`. + * Processed configuration from the [configuration file](https://playwright.dev/docs/test-configuration). + */ + config: FullConfig; + /** + * Processed project configuration from the [configuration file](https://playwright.dev/docs/test-configuration). + */ + project: FullProject; + /** + * The unique index of the worker process that is running the test. Also available as `process.env.TEST_WORKER_INDEX`. + * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. + */ + workerIndex: number; + + /** + * The title of the currently running test as passed to `test(title, testFunction)`. */ title: string; - /** - * Path to the file where test is declared. + * Absolute path to a file where the currently running test is declared. */ file: string; - /** - * Line number in the test file where the test is declared. + * Line number where the currently running test is declared. */ line: number; - /** - * Column number in the test file where the test is declared. + * Column number where the currently running test is declared. */ column: number; - /** - * The test function as passed to `test('my test title', testFunction)`. + * Test function as passed to `test(title, testFunction)`. */ fn: Function; /** - * Call this method to skip the current test. + * Skips the currently running test. This is similar to + * [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip). + * @param condition Optional condition - the test is skipped when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ skip(): void; skip(condition: boolean): void; skip(condition: boolean, description: string): void; /** - * Call this method to mark the current test as "needs to be fixed". The test will not be run. + * Marks the currently running test as "fixme". The test will be skipped, but the intention is to fix it. This is similar + * to [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme). + * @param condition Optional condition - the test is marked as "fixme" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ fixme(): void; fixme(condition: boolean): void; fixme(condition: boolean, description: string): void; /** - * Call this method to mark the current test as "expected to fail". The test will be run and must fail. + * Marks the currently running test as "should fail". Playwright Test ensures that this test is actually failing. This is + * similar to [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail). + * @param condition Optional condition - the test is marked as "should fail" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ fail(): void; fail(condition: boolean): void; fail(condition: boolean, description: string): void; /** - * Call this method to mark the current test as slow. The default timeout will be trippled. + * Marks the currently running test as "slow", giving it triple the default timeout. This is similar to + * [test.slow([condition, description])](https://playwright.dev/docs/api/class-test#test-slow). + * @param condition Optional condition - the test is marked as "slow" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ slow(): void; slow(condition: boolean): void; slow(condition: boolean, description: string): void; /** - * Call this method to set a custom timeout for the current test. + * Changes the timeout for the currently running test. Zero means no timeout. + * + * Timeout is usually specified in the [configuration file](https://playwright.dev/docs/test-configuration), but it could be useful to change the + * timeout in certain scenarios: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * @param timeout Timeout in milliseconds. */ setTimeout(timeout: number): void; - /** - * The expected status for the test: - * - `'passed'` for most tests; - * - `'failed'` for tests marked with `test.fail()`; - * - `'skipped'` for tests marked with `test.skip()` or `test.fixme()`. + * Expected status for the currently running test. This is usually `'passed'`, except for a few cases: + * - `'skipped'` for skipped tests, e.g. with + * [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip); + * - `'failed'` for tests marked as failed with + * [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail). + * + * Expected status is usually compared with the actual + * [testInfo.status](https://playwright.dev/docs/api/class-testinfo#test-info-status): + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * */ expectedStatus: TestStatus; - /** - * Timeout in milliseconds for this test. + * Timeout in milliseconds for the currently running test. Zero means no timeout. Timeout is usually specified in the + * [configuration file](https://playwright.dev/docs/test-configuration) + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * testInfo.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * */ timeout: number; - /** - * Annotations collected for this test. + * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups the test belongs to + * and file-level annotations for the test file. + * + * Learn more about [test annotations](https://playwright.dev/docs/test-annotations). */ annotations: { type: string, description?: string }[]; - /** - * File attachments for this test. + * The list of files or buffers attached to the current test. Some reporters show test attachments. For example, you can + * attach a screenshot to the test. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }, testInfo) => { + * await page.goto('https://playwright.dev'); + * + * // Capture a screenshot and attach it. + * const path = testInfo.outputPath('screenshot.png'); + * await page.screenshot({ path }); + * testInfo.attachments.push({ name: 'screenshot', path, contentType: 'image/png' }); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }, testInfo) => { + * await page.goto('https://playwright.dev'); + * + * // Capture a screenshot and attach it. + * const path = testInfo.outputPath('screenshot.png'); + * await page.screenshot({ path }); + * testInfo.attachments.push({ name: 'screenshot', path, contentType: 'image/png' }); + * }); + * ``` + * */ attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; - /** - * When tests are run multiple times, each run gets a unique `repeatEachIndex`. + * Specifies a unique repeat index when running in "repeat each" mode. This mode is enabled by passing `--repeat-each` to + * the [command line](https://playwright.dev/docs/test-cli). */ repeatEachIndex: number; - /** - * When the test is retried after a failure, `retry` indicates the attempt number. - * Zero for the first (non-retry) run. - * - * The maximum number of retries is configurable with `retries` field in the config. + * Specifies the retry number when the test is retried after a failure. The first test run has + * [testInfo.retry](https://playwright.dev/docs/api/class-testinfo#test-info-retry) equal to zero, the first retry has it + * equal to one, and so on. Learn more about [retries](https://playwright.dev/docs/test-retries). */ retry: number; - /** - * The number of milliseconds this test took to finish. - * Only available after the test has finished. + * The number of milliseconds the test took to finish. Always zero before the test finishes, either successfully or not. */ duration: number; - /** - * The result of the run. - * Only available after the test has finished. + * Actual status for the currently running test. Available after the test has finished in + * [test.afterEach(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-each) hook and fixtures. + * + * Status is usually compared with the + * [testInfo.expectedStatus](https://playwright.dev/docs/api/class-testinfo#test-info-expected-status): + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.afterEach(async ({}, testInfo) => { + * if (testInfo.status !== testInfo.expectedStatus) + * console.log(`${testInfo.title} did not run as expected!`); + * }); + * ``` + * */ status?: TestStatus; - /** - * The error thrown by the test if any. - * Only available after the test has finished. + * An error thrown during test execution, if any. */ error?: TestError; - /** - * Output written to `process.stdout` or `console.log` from the test. - * Only available after the test has finished. + * Output written to `process.stdout` or `console.log` during the test execution. */ stdout: (string | Buffer)[]; - /** - * Output written to `process.stderr` or `console.error` from the test. - * Only available after the test has finished. + * Output written to `process.stderr` or `console.error` during the test execution. */ stderr: (string | Buffer)[]; - /** - * Suffix used to differentiate snapshots between multiple test configurations. - * For example, if snapshots depend on the platform, you can set `testInfo.snapshotSuffix = process.platform`, - * and `expect(value).toMatchSnapshot(snapshotName)` will use different snapshots depending on the platform. + * Suffix used to differentiate snapshots between multiple test configurations. For example, if snapshots depend on the + * platform, you can set `testInfo.snapshotSuffix` equal to `process.platform`. In this case + * `expect(value).toMatchSnapshot(snapshotName)` will use different snapshots depending on the platform. Learn more about + * [snapshots](https://playwright.dev/docs/test-snapshots). */ snapshotSuffix: string; - /** - * Absolute path to the output directory for this specific test run. - * Each test gets its own directory. + * Absolute path to the output directory for this specific test run. Each test run gets its own directory so they cannot + * conflict. */ outputDir: string; - /** - * Returns a path to a snapshot file. + * Returns a path to a snapshot file with the given `snapshotName`. Learn more about [snapshots](https://playwright.dev/docs/test-snapshots). + * @param snapshotName */ snapshotPath: (snapshotName: string) => string; - /** - * Returns a path inside the `outputDir` where the test can safely put a temporary file. - * Guarantees that tests running in parallel will not interfere with each other. + * Returns a path inside the [testInfo.outputDir](https://playwright.dev/docs/api/class-testinfo#test-info-output-dir) + * where the test can safely put a temporary file. Guarantees that tests running in parallel will not interfere with each + * other. * - * ```js - * const file = testInfo.outputPath('temporary-file.txt'); - * await fs.promises.writeFile(file, 'Put some data to the file', 'utf8'); + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * const fs = require('fs'); + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('dir', 'temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the dir/temporary-file.txt', 'utf8'); + * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * import fs from 'fs'; + * + * test('example test', async ({}, testInfo) => { + * const file = testInfo.outputPath('dir', 'temporary-file.txt'); + * await fs.promises.writeFile(file, 'Put some data to the dir/temporary-file.txt', 'utf8'); + * }); + * ``` + * + * @param pathSegments Path segments to append at the end of the resulting path. */ outputPath: (...pathSegments: string[]) => string; } interface SuiteFunction { - (name: string, inner: () => void): void; + (title: string, callback: () => void): void; } interface TestFunction { - (name: string, inner: (args: TestArgs, testInfo: TestInfo) => Promise | void): void; + (title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise | void): void; } /** - * Call this function to declare a test. + * Playwright Test provides a `test` function to declare tests and [`expect` function](https://jestjs.io/docs/expect) to + * write assertions. * - * ```js - * test('my test title', async () => { - * // Test code goes here. + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * await page.goto('https://playwright.dev/'); + * const name = await page.innerText('.navbar__title'); + * expect(name).toBe('Playwright'); * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * await page.goto('https://playwright.dev/'); + * const name = await page.innerText('.navbar__title'); + * expect(name).toBe('Playwright'); + * }); + * ``` + * */ export interface TestType extends TestFunction { /** - * Use `test.only()` instead of `test()` to ignore all other tests and only run this one. - * Useful for debugging a particular test. + * Declares a focused test. If there are some focused tests or suites, all of them will be run but nothing else. * - * ```js - * test.only('my test title', async () => { - * // Only this test will run. + * ```js js-flavor=js + * test.only('focus this test', async ({ page }) => { + * // Run only focused tests in the entire project. * }); * ``` * - * All tests marked as `test.only()` will be run, so you can mark multiple of them. + * ```js js-flavor=ts + * test.only('focus this test', async ({ page }) => { + * // Run only focused tests in the entire project. + * }); + * ``` + * + * @param title Test title. + * @param testFunction Test function that takes one or two arguments: an object with fixtures and optional [TestInfo]. */ only: TestFunction; - /** - * Declare a block of related tests. + * Declares a group of tests. * - * ```js - * test.decribe('my test suite', () => { - * test('one test', async () => { - * // Test code goes here. + * ```js js-flavor=js + * test.describe('two tests', () => { + * test('one', async ({ page }) => { + * // ... * }); * - * test('another test', async () => { - * // Test code goes here. + * test('two', async ({ page }) => { + * // ... * }); * }); * ``` * - * Any `beforeEach`, `afterEach`, `beforeAll` and `afterAll` hooks declared inside the `test.decribe()` block - * will only affect the tests from this block. + * ```js js-flavor=ts + * test.describe('two tests', () => { + * test('one', async ({ page }) => { + * // ... + * }); + * + * test('two', async ({ page }) => { + * // ... + * }); + * }); + * ``` + * + * @param title Group title. + * @param callback A callback that is run immediately when calling [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe). Any tests added in this + * callback will belong to the group. */ describe: SuiteFunction & { - /** - * Use `test.describe.only()` instead of `test.describe()` to ignore all other tests and only run this block. - * Useful for debugging a few tests. - */ only: SuiteFunction; }; - /** - * Skip running this test. + * Skips a test or a group of tests. * - * ```js - * test('my test title', async () => { + * Unconditionally skip a test: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('broken test', async ({ page }) => { * test.skip(); - * // Test code goes here. It will not be executed. + * // ... * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('broken test', async ({ page }) => { + * test.skip(); + * // ... + * }); + * ``` + * + * Conditionally skip a test with an optional description: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('skip in WebKit', async ({ page, browserName }) => { + * test.skip(browserName === 'webkit', 'This feature is not implemented for Mac'); + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('skip in WebKit', async ({ page, browserName }) => { + * test.skip(browserName === 'webkit', 'This feature is not implemented for Mac'); + * // ... + * }); + * ``` + * + * Conditionally skip all tests in a file or + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.skip(({ browserName }) => browserName === 'webkit'); + * + * test('skip in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('skip in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.skip(({ browserName }) => browserName === 'webkit'); + * + * test('skip in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('skip in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * Skip from a hook: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }) => { + * test.skip(process.env.APP_VERSION === 'v1', 'There are no settings in v1'); + * await page.goto('/settings'); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }) => { + * test.skip(process.env.APP_VERSION === 'v1', 'There are no settings in v1'); + * await page.goto('/settings'); + * }); + * ``` + * + * @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are skipped when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ skip(): void; - - /** - * Skip running this test when `condition` is true. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.skip(browserName === 'webkit'); - * // Test code goes here. It will not be executed in WebKit. - * }); - * ``` - */ skip(condition: boolean): void; - - /** - * Skip running this test when `condition` is true. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.skip(browserName === 'webkit', 'Layout is funky'); - * // Test code goes here. It will not be executed in WebKit. - * }); - * ``` - */ skip(condition: boolean, description: string): void; - - /** - * Skip running tests in the `describe` block based on some condition. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.skip(() => browserName === 'webkit'); - * - * // Declare tests below - they will not be executed in WebKit. - * }); - * ``` - */ skip(callback: (args: TestArgs & WorkerArgs) => boolean): void; - - /** - * Skip running tests in the `describe` block based on some condition. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.skip(() => browserName === 'webkit', 'Layout is funky'); - * - * // Declare tests below - they will not be executed in WebKit. - * }); - * ``` - */ skip(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; - /** - * Skip running this test, with intention to fix it later. + * Marks a test or a group of tests as "fixme". These tests will not be run, but the intention is to fix them. * - * ```js - * test('my test title', async () => { + * Unconditional fixme: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('not yet ready', async ({ page }) => { * test.fixme(); - * // Test code goes here. It will not be executed. + * // ... * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('not yet ready', async ({ page }) => { + * test.fixme(); + * // ... + * }); + * ``` + * + * Conditional fixme a test with an optional description: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('fixme in WebKit', async ({ page, browserName }) => { + * test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet'); + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('fixme in WebKit', async ({ page, browserName }) => { + * test.fixme(browserName === 'webkit', 'This feature is not implemented for Mac yet'); + * // ... + * }); + * ``` + * + * Conditional fixme for all tests in a file or + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.fixme(({ browserName }) => browserName === 'webkit'); + * + * test('fixme in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('fixme in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.fixme(({ browserName }) => browserName === 'webkit'); + * + * test('fixme in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('fixme in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * `fixme` from a hook: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }) => { + * test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet'); + * await page.goto('/settings'); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }) => { + * test.fixme(process.env.APP_VERSION === 'v2', 'No settings in v2 yet'); + * await page.goto('/settings'); + * }); + * ``` + * + * @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "fixme" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ fixme(): void; - - /** - * Skip running this test when `condition` is true, with intention to fix it later. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.fixme(browserName === 'webkit'); - * // Test code goes here. It will not be executed in WebKit. - * }); - * ``` - */ fixme(condition: boolean): void; - - /** - * Skip running this test when `condition` is true, with intention to fix it later. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.fixme(browserName === 'webkit', 'Layout is funky'); - * // Test code goes here. It will not be executed in WebKit. - * }); - * ``` - */ fixme(condition: boolean, description: string): void; - - /** - * Skip running tests in the `describe` block based on some condition, with intention to fix it later. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.fixme(() => browserName === 'webkit'); - * - * // Declare tests below - they will not be executed in WebKit. - * }); - * ``` - */ fixme(callback: (args: TestArgs & WorkerArgs) => boolean): void; - - /** - * Skip running tests in the `describe` block based on some condition, with intention to fix it later. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.fixme(() => browserName === 'webkit', 'Layout is funky'); - * - * // Declare tests below - they will not be executed in WebKit. - * }); - * ``` - */ fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; - /** - * Mark the test as "expected to fail". It will be run and should fail. - * When "expected to fail" test acceidentally passes, test runner will exit with an error. + * Marks a test or a group of tests as "should fail". Playwright Test runs these tests and ensures that they are actually + * failing. This is useful for documentation purposes to acknowledge that some functionality is broken until it is fixed. * - * ```js - * test('my test title', async () => { + * Unconditional fail: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('not yet ready', async ({ page }) => { * test.fail(); - * // Test code goes here. + * // ... * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('not yet ready', async ({ page }) => { + * test.fail(); + * // ... + * }); + * ``` + * + * Conditional fail a test with an optional description: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('fail in WebKit', async ({ page, browserName }) => { + * test.fail(browserName === 'webkit', 'This feature is not implemented for Mac yet'); + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('fail in WebKit', async ({ page, browserName }) => { + * test.fail(browserName === 'webkit', 'This feature is not implemented for Mac yet'); + * // ... + * }); + * ``` + * + * Conditional fail for all tests in a file or + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.fail(({ browserName }) => browserName === 'webkit'); + * + * test('fail in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('fail in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.fail(({ browserName }) => browserName === 'webkit'); + * + * test('fail in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('fail in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "should fail" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ fail(): void; - - /** - * Mark the test as "expected to fail", when `condition` is true. It will be run and should fail. - * When "expected to fail" test acceidentally passes, test runner will exit with an error. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.fail(browserName === 'webkit'); - * // Test code goes here. It should fail in WebKit. - * }); - * ``` - */ fail(condition: boolean): void; - - /** - * Mark the test as "expected to fail", when `condition` is true. It will be run and should fail. - * When "expected to fail" test acceidentally passes, test runner will exit with an error. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.fail(browserName === 'webkit', 'Layout is funky, see issue #1234'); - * // Test code goes here. It should fail in WebKit. - * }); - * ``` - */ fail(condition: boolean, description: string): void; - - /** - * Mark tests in the `describe` block as "expected to fail" based on some condition. - * The tests will be run and should fail. - * When "expected to fail" test acceidentally passes, test runner will exit with an error. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.fail(() => browserName === 'webkit'); - * - * // Declare tests below - they should fail in WebKit. - * }); - * ``` - */ fail(callback: (args: TestArgs & WorkerArgs) => boolean): void; - - /** - * Mark tests in the `describe` block as "expected to fail" based on some condition. - * The tests will be run and should fail. - * When "expected to fail" test acceidentally passes, test runner will exit with an error. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.fail(() => browserName === 'webkit', 'Layout is funky, see issue #1234'); - * - * // Declare tests below - they should fail in WebKit. - * }); - * ``` - */ fail(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; - /** - * Triples the default timeout for this test. + * Marks a test or a group of tests as "slow". Slow tests will be given triple the default timeout. * - * ```js - * test('my test title', async () => { + * Unconditional slow: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('slow test', async ({ page }) => { * test.slow(); - * // Test code goes here. + * // ... * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('slow test', async ({ page }) => { + * test.slow(); + * // ... + * }); + * ``` + * + * Conditional slow a test with an optional description: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('slow in WebKit', async ({ page, browserName }) => { + * test.slow(browserName === 'webkit', 'This feature is slow on Mac'); + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('slow in WebKit', async ({ page, browserName }) => { + * test.slow(browserName === 'webkit', 'This feature is slow on Mac'); + * // ... + * }); + * ``` + * + * Conditional slow for all tests in a file or + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.slow(({ browserName }) => browserName === 'webkit'); + * + * test('slow in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('slow in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.slow(({ browserName }) => browserName === 'webkit'); + * + * test('slow in WebKit 1', async ({ page }) => { + * // ... + * }); + * test('fail in WebKit 2', async ({ page }) => { + * // ... + * }); + * ``` + * + * @param condition Optional condition - either a boolean value, or a function that takes a fixtures object and returns a boolean. Test or tests are marked as "slow" when the condition is `true`. + * @param description Optional description that will be reflected in a test report. */ slow(): void; - - /** - * Triples the default timeout for this test, when `condition` is true. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.slow(browserName === 'webkit'); - * // Test code goes here. It will be given triple timeout in WebKit. - * }); - * ``` - */ slow(condition: boolean): void; - - /** - * Triples the default timeout for this test, when `condition` is true. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test('my test title', async ({ browserName }) => { - * test.slow(browserName === 'webkit', 'See issue #1234'); - * // Test code goes here. It will be given triple timeout in WebKit. - * }); - * ``` - */ slow(condition: boolean, description: string): void; - - /** - * Give all tests in the `describe` block triple timeout, based on some condition. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.slow(() => browserName === 'webkit'); - * - * // Declare tests below - they will be given triple timeout in WebKit. - * }); - * ``` - */ slow(callback: (args: TestArgs & WorkerArgs) => boolean): void; - - /** - * Give all tests in the `describe` block triple timeout, based on some condition. - * Put a reason in `description` to easily remember it later. - * - * ```js - * test.describe('my tests', ({ browserName }) => { - * test.slow(() => browserName === 'webkit', 'See issue #1234'); - * - * // Declare tests below - they will be given triple timeout in WebKit. - * }); - * ``` - */ slow(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; - /** - * Set a custom timeout for the test. + * Changes the timeout for the test. * - * ```js - * test('my test title', async () => { - * // Give this test 20 seconds. - * test.setTimeout(20000); - * // Test code goes here. + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('very slow test', async ({ page }) => { + * test.setTimeout(120000); + * // ... * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('very slow test', async ({ page }) => { + * test.setTimeout(120000); + * // ... + * }); + * ``` + * + * Changing timeout from a slow hook: + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * test.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }, testInfo) => { + * // Extend timeout for all tests running this hook by 30 seconds. + * test.setTimeout(testInfo.timeout + 30000); + * }); + * ``` + * + * Timeout for the currently running test is available through + * [testInfo.timeout](https://playwright.dev/docs/api/class-testinfo#test-info-timeout). + * @param timeout Timeout in milliseconds. */ setTimeout(timeout: number): void; - /** - * Declare a hook that will be run before each test. - * It may use all the available fixtures. + * Declares a `beforeEach` hook that is executed before each test. When called in the scope of a test file, runs before + * each test in the file. When called inside a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group, runs before each test + * in the group. * - * ```js - * test.beforeEach(async ({ fixture }, testInfo) => { - * // Do some work here. + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeEach(async ({ page }) => { + * // Go to the starting url before each test. + * await page.goto('https://my.start.url/'); + * }); + * + * test('my test', async ({ page }) => { + * expect(page.url()).toBe('https://my.start.url/'); * }); * ``` * - * When called inside a `test.describe()` block, the hook only applies to the tests from the block. + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeEach(async ({ page }) => { + * // Go to the starting url before each test. + * await page.goto('https://my.start.url/'); + * }); + * + * test('my test', async ({ page }) => { + * expect(page.url()).toBe('https://my.start.url/'); + * }); + * ``` + * + * You can use [test.afterEach(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-each) to teardown any + * resources set up in `beforeEach`. + * @param hookFunction Hook function that takes one or two arguments: an object with fixtures and optional [TestInfo]. */ beforeEach(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise | any): void; - /** - * Declare a hook that will be run after each test. - * It may use all the available fixtures. - * - * ```js - * test.afterEach(async ({ fixture }, testInfo) => { - * // Do some work here. - * }); - * ``` - * - * When called inside a `test.describe()` block, the hook only applies to the tests from the block. + * Declares an `afterEach` hook that is executed after each test. When called in the scope of a test file, runs before each + * test in the file. When called inside a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group, runs before each test + * in the group. + * @param hookFunction Hook function that takes one or two arguments: an object with fixtures and optional [TestInfo]. */ afterEach(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise | any): void; - /** - * Declare a hook that will be run once before all tests in the file. - * It may use all worker-scoped fixtures. + * Declares a `beforeAll` hook that is executed once before all tests. When called in the scope of a test file, runs before + * all tests in the file. When called inside a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group, runs before all tests + * in the group. * - * ```js - * test.beforeAll(async ({ workerFixture }, workerInfo) => { - * // Do some work here. + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * test.beforeAll(async () => { + * console.log('Before tests'); + * }); + * + * test.afterAll(async () => { + * console.log('After tests'); + * }); + * + * test('my test', async ({ page }) => { + * // ... * }); * ``` * - * When called inside a `test.describe()` block, the hook only applies to the tests from the block. + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * test.beforeAll(async () => { + * console.log('Before tests'); + * }); + * + * test.afterAll(async () => { + * console.log('After tests'); + * }); + * + * test('my test', async ({ page }) => { + * // ... + * }); + * ``` + * + * You can use [test.afterAll(hookFunction)](https://playwright.dev/docs/api/class-test#test-after-all) to teardown any + * resources set up in `beforeAll`. + * @param hookFunction Hook function that takes one or two arguments: an object with fixtures and optional [WorkerInfo]. */ beforeAll(inner: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise | any): void; - /** - * Declare a hook that will be run once after all tests in the file. - * It may use all worker-scoped fixtures. - * - * ```js - * test.afterAll(async ({ workerFixture }, workerInfo) => { - * // Do some work here. - * }); - * ``` - * - * When called inside a `test.describe()` block, the hook only applies to the tests from the block. + * Declares an `afterAll` hook that is executed once after all tests. When called in the scope of a test file, runs after + * all tests in the file. When called inside a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group, runs after all tests + * in the group. + * @param hookFunction Hook function that takes one or two arguments: an object with fixtures and optional [WorkerInfo]. */ afterAll(inner: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise | any): void; - /** - * Declare fixtures/options to be used for tests in this file. + * Specifies parameters or fixtures to use in a single test file or a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group. Most useful to + * configure a fixture, for example set `locale` to configure `context` fixture. * - * ```js - * test.use({ ignoreHTTPSErrors: true }); + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); * - * test('my test title', async ({ page }) => { - * // Test code goes here. + * test.use({ locale: 'en-US' }); + * + * test('test with locale', async ({ page }) => { + * // Default context and page have locale as specified * }); * ``` * - * When called inside a `test.describe()` block, fixtures/options only apply to the tests from the block. - */ - use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void; - - /** - * Use `test.expect(value).toBe(expected)` to assert something in the test. - * See [expect library](https://jestjs.io/docs/expect) documentation for more details. - */ - expect: Expect; - - declare(): TestType; - - /** - * Extend the test with fixtures. These fixtures will be invoked for test when needed, - * can perform setup/teardown and provide a resource to the test. + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; * - * ```ts - * import { test as base } from '@playwright/test'; - * import rimraf from 'rimraf'; + * test.use({ locale: 'en-US' }); * - * const test = base.extend<{ dirCount: number, dirs: string[] }>({ - * // Define an option that can be configured in tests with `test.use()`. - * // Provide a default value. - * dirCount: 1, + * test('test with locale', async ({ page }) => { + * // Default context and page have locale as specified + * }); + * ``` * - * // Define a fixture that provides some useful functionality to the test. - * // In this example, it will create some temporary directories. - * dirs: async ({ dirCount }, use, testInfo) => { - * // Our fixture uses the "dirCount" option that can be configured by the test. - * const dirs = []; - * for (let i = 0; i < dirCount; i++) { - * // Create an isolated directory. - * const dir = testInfo.outputPath('dir-' + i); - * await fs.promises.mkdir(dir, { recursive: true }); - * dirs.push(dir); - * } + * It is possible not only to provide a fixture value, but also to override a fixture by providing a fixture function. * - * // Use the list of directories in the test. - * await use(dirs); + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); * - * // Cleanup if needed. - * for (const dir of dirs) - * await new Promise(done => rimraf(dir, done)); + * test.use({ + * locale: async ({}, use) => { + * // Read locale from some configuration file. + * const locale = await fs.promises.readFile('test-locale', 'utf-8'); + * await use(locale); * }, * }); * - * - * // Tests in this file need two temporary directories. - * test.use({ dirCount: 2 }); - * - * test('my test title', async ({ dirs }) => { - * // Test code goes here. - * // It can use "dirs" right away - the fixture has already run and created two temporary directories. + * test('test with locale', async ({ page }) => { + * // Default context and page have locale as specified * }); * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test.use({ + * locale: async ({}, use) => { + * // Read locale from some configuration file. + * const locale = await fs.promises.readFile('test-locale', 'utf-8'); + * await use(locale); + * }, + * }); + * + * test('test with locale', async ({ page }) => { + * // Default context and page have locale as specified + * }); + * ``` + * + * @param fixtures An object with fixture definitions. */ + use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void; + /** + * `expect` function can be used to create test assertions. Read + * [expect library documentation](https://jestjs.io/docs/expect) for more details. + */ + expect: Expect; + declare(): TestType; extend(fixtures: Fixtures): TestType; } @@ -954,284 +2117,606 @@ export type Fixtures | [TestFixtureValue, { scope?: 'test', auto?: boolean }]; }; -/** - * The name of the browser supported by Playwright. - */ type BrowserName = 'chromium' | 'firefox' | 'webkit'; - -/** - * Browser channel name. Used to run tests in different browser flavors, - * for example Google Chrome Beta, or Microsoft Edge Stable. - * @see BrowserContextOptions - */ type BrowserChannel = Exclude; - -/** - * Emulates `'prefers-colors-scheme'` media feature, - * supported values are `'light'`, `'dark'`, `'no-preference'`. - * @see BrowserContextOptions - */ type ColorScheme = Exclude; - -/** - * An object containing additional HTTP headers to be sent with every request. All header values must be strings. - * @see BrowserContextOptions - */ type ExtraHTTPHeaders = Exclude; - -/** - * Proxy settings available for all tests, or individually per test. - * @see BrowserContextOptions - */ type Proxy = Exclude; - -/** - * Storage state for the test. - * @see BrowserContextOptions - */ type StorageState = Exclude; /** - * Options available to configure browser launch. - * - Set options in config: - * ```js - * use: { browserName: 'webkit' } - * ``` - * - Set options in test file: - * ```js - * test.use({ browserName: 'webkit' }) - * ``` + * Playwright Test is based on the concept of the [test fixtures](https://playwright.dev/docs/test-fixtures). Test fixtures are used to establish + * environment for each test, giving the test everything it needs and nothing else. + * + * Playwright Test looks at each test declaration, analyses the set of fixtures the test needs and prepares those fixtures + * specifically for the test. Values prepared by the fixtures are merged into a single object that is available to the + * `test`, hooks, annotations and other fixtures as a first parameter. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * Given the test above, Playwright Test will set up the `page` fixture before running the test, and tear it down after the + * test has finished. `page` fixture provides a [Page] object that is available to the test. + * + * Playwright Test comes with builtin fixtures listed below, and you can add your own fixtures as well. Many fixtures are + * designed as "options" that you can set in your + * [testConfig.use](https://playwright.dev/docs/api/class-testconfig#test-config-use) section. + * + * ```js js-flavor=js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * const config: PlaywrightTestConfig = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * export default config; + * ``` + * + * Alternatively, with [test.use(fixtures)](https://playwright.dev/docs/api/class-test#test-use) you can override some + * options for a file. + * + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` * - * Available as arguments to the test function and all hooks (beforeEach, afterEach, beforeAll, afterAll). */ -export type PlaywrightWorkerOptions = { +export interface PlaywrightWorkerOptions { /** - * Name of the browser (`chromium`, `firefox`, `webkit`) that runs tests. + * Name of the browser that runs tests. Defaults to `'chromium'`. Most of the time you should set `browserName` in your + * [TestConfig]: + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * browserName: 'firefox', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig, devices } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * use: { + * browserName: 'firefox', + * }, + * }; + * export default config; + * ``` + * */ browserName: BrowserName; defaultBrowserType: BrowserName; - /** - * Whether to run browser in headless mode. Takes priority over `launchOptions`. - * @see LaunchOptions + * Whether to run browser in headless mode. More details for + * [Chromium](https://developers.google.com/web/updates/2017/04/headless-chrome) and + * [Firefox](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode). Defaults to `true` unless the + * `devtools` option is `true`. */ headless: boolean | undefined; - /** - * Browser distribution channel. Takes priority over `launchOptions`. - * @see LaunchOptions + * Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", + * "msedge-beta", "msedge-dev", "msedge-canary". Read more about using + * [Google Chrome and Microsoft Edge](https://playwright.dev/docs/browsers#google-chrome--microsoft-edge). */ channel: BrowserChannel | undefined; - /** - * Options used to launch the browser. Other options above (e.g. `headless`) take priority. - * @see LaunchOptions + * Options used to launch the browser, as passed to + * [browserType.launch([options])](https://playwright.dev/docs/api/class-browsertype#browser-type-launch). Specific options + * [fixtures.headless](https://playwright.dev/docs/api/class-fixtures#fixtures-headless) and + * [fixtures.channel](https://playwright.dev/docs/api/class-fixtures#fixtures-channel) take priority over this. */ launchOptions: LaunchOptions; -}; +} -/** - * Video recording mode: - * - `off`: Do not record video. - * - `on`: Record video for each test. - * - `retain-on-failure`: Record video for each test, but remove all videos from successful test runs. - * - `on-first-retry`: Record video only when retrying a test for the first time. - */ export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-video'; /** - * Options available to configure each test. - * - Set options in config: - * ```js - * use: { video: 'on' } - * ``` - * - Set options in test file: - * ```js - * test.use({ video: 'on' }) - * ``` + * Playwright Test is based on the concept of the [test fixtures](https://playwright.dev/docs/test-fixtures). Test fixtures are used to establish + * environment for each test, giving the test everything it needs and nothing else. + * + * Playwright Test looks at each test declaration, analyses the set of fixtures the test needs and prepares those fixtures + * specifically for the test. Values prepared by the fixtures are merged into a single object that is available to the + * `test`, hooks, annotations and other fixtures as a first parameter. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * Given the test above, Playwright Test will set up the `page` fixture before running the test, and tear it down after the + * test has finished. `page` fixture provides a [Page] object that is available to the test. + * + * Playwright Test comes with builtin fixtures listed below, and you can add your own fixtures as well. Many fixtures are + * designed as "options" that you can set in your + * [testConfig.use](https://playwright.dev/docs/api/class-testconfig#test-config-use) section. + * + * ```js js-flavor=js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * const config: PlaywrightTestConfig = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * export default config; + * ``` + * + * Alternatively, with [test.use(fixtures)](https://playwright.dev/docs/api/class-test#test-use) you can override some + * options for a file. + * + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` * - * Available as arguments to the test function and beforeEach/afterEach hooks. */ -export type PlaywrightTestOptions = { +export interface PlaywrightTestOptions { /** - * Whether to capture a screenshot after each test, off by default. - * - `off`: Do not capture screenshots. - * - `on`: Capture screenshot after each test. - * - `only-on-failure`: Capture screenshot after each test failure. + * Whether to automatically capture a screenshot after each test. Defaults to `'off'`. + * - `'off'`: Do not capture screenshots. + * - `'on'`: Capture screenshot after each test. + * - `'only-on-failure'`: Capture screenshot after each test failure. + * + * Learn more about [automatic screenshots](https://playwright.dev/docs/test-configuration#automatic-screenshots). */ screenshot: 'off' | 'on' | 'only-on-failure'; - /** - * Whether to record trace for each test, off by default. - * - `off`: Do not record trace. - * - `on`: Record trace for each test. - * - `retain-on-failure`: Record trace for each test, but remove trace from successful test run. - * - `on-first-retry`: Record trace only when retrying a test for the first time. + * Whether to record a trace for each test. Defaults to `'off'`. + * - `'off'`: Do not record a trace. + * - `'on'`: Record a trace for each test. + * - `'retain-on-failure'`: Record a trace for each test, but remove it from successful test runs. + * - `'on-first-retry'`: Record a trace only when retrying a test for the first time. + * + * Learn more about [recording trace](https://playwright.dev/docs/test-configuration#record-test-trace). */ trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace'; - /** - * Whether to record video for each test, off by default. + * Whether to record video for each test. Defaults to `'off'`. + * - `'off'`: Do not record video. + * - `'on'`: Record video for each test. + * - `'retain-on-failure'`: Record video for each test, but remove all videos from successful test runs. + * - `'on-first-retry'`: Record video only when retrying a test for the first time. + * + * Learn more about [recording video](https://playwright.dev/docs/test-configuration#record-video). */ video: VideoMode | { mode: VideoMode, size: ViewportSize }; - /** - * Whether to automatically download all the attachments. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Whether to automatically download all the attachments. Defaults to `false` where all the downloads are canceled. */ acceptDownloads: boolean | undefined; - /** - * Toggles bypassing page's Content-Security-Policy. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Toggles bypassing page's Content-Security-Policy. */ bypassCSP: boolean | undefined; - /** - * Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. - * @see BrowserContextOptions + * Emulates `'prefers-colors-scheme'` media feature, supported values are `'light'`, `'dark'`, `'no-preference'`. See + * [page.emulateMedia([options])](https://playwright.dev/docs/api/class-page#page-emulate-media) for more details. Defaults + * to `'light'`. */ colorScheme: ColorScheme | undefined; - /** * Specify device scale factor (can be thought of as dpr). Defaults to `1`. - * @see BrowserContextOptions */ deviceScaleFactor: number | undefined; - /** * An object containing additional HTTP headers to be sent with every request. All header values must be strings. - * @see BrowserContextOptions */ extraHTTPHeaders: ExtraHTTPHeaders | undefined; - - /** - * Context geolocation. Takes priority over `contextOptions`. - * @see BrowserContextOptions - */ geolocation: Geolocation | undefined; - /** - * Specifies if viewport supports touch events. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Specifies if viewport supports touch events. Defaults to false. */ hasTouch: boolean | undefined; - /** * Credentials for [HTTP authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication). - * @see BrowserContextOptions */ httpCredentials: HTTPCredentials | undefined; - /** - * Whether to ignore HTTPS errors during navigation. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Whether to ignore HTTPS errors during navigation. Defaults to `false`. */ ignoreHTTPSErrors: boolean | undefined; - /** - * Whether the `meta viewport` tag is taken into account and touch events are enabled. Not supported in Firefox. - * @see BrowserContextOptions + * Whether the `meta viewport` tag is taken into account and touch events are enabled. Defaults to `false`. Not supported + * in Firefox. */ isMobile: boolean | undefined; - /** * Whether or not to enable JavaScript in the context. Defaults to `true`. - * @see BrowserContextOptions */ javaScriptEnabled: boolean | undefined; - /** - * User locale, for example `en-GB`, `de-DE`, etc. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Specify user locale, for example `en-GB`, `de-DE`, etc. Locale will affect `navigator.language` value, `Accept-Language` + * request header value as well as number and date formatting rules. */ locale: string | undefined; - /** - * Whether to emulate network being offline. - * @see BrowserContextOptions + * Whether to emulate network being offline. Defaults to `false`. */ offline: boolean | undefined; - /** - * A list of permissions to grant to all pages in this context. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * A list of permissions to grant to all pages in this context. See + * [browserContext.grantPermissions(permissions[, options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-grant-permissions) + * for more details. */ permissions: string[] | undefined; - /** - * Proxy setting used for all pages in the test. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Network proxy settings. */ proxy: Proxy | undefined; - /** - * Populates context with given storage state. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Populates context with given storage state. This option can be used to initialize context with logged-in information + * obtained via + * [browserContext.storageState([options])](https://playwright.dev/docs/api/class-browsercontext#browser-context-storage-state). + * Either a path to the file with saved storage, or an object with the following fields: */ storageState: StorageState | undefined; - /** - * Changes the timezone of the context. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Changes the timezone of the context. See + * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1) + * for a list of supported timezone IDs. */ timezoneId: string | undefined; - /** * Specific user agent to use in this context. - * @see BrowserContextOptions */ userAgent: string | undefined; - /** - * Viewport used for all pages in the test. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. `null` disables the default viewport. */ viewport: ViewportSize | null | undefined; - /** - * `baseURL` used for all pages in the test. Takes priority over `contextOptions`. - * @see BrowserContextOptions + * When using [page.goto(url[, options])](https://playwright.dev/docs/api/class-page#page-goto), + * [page.route(url, handler)](https://playwright.dev/docs/api/class-page#page-route), + * [page.waitForURL(url[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-url), + * [page.waitForRequest(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-request), or + * [page.waitForResponse(urlOrPredicate[, options])](https://playwright.dev/docs/api/class-page#page-wait-for-response) it + * takes the base URL in consideration by using the [`URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) + * constructor for building the corresponding URL. Examples: + * - baseURL: `http://localhost:3000` and navigating to `/bar.html` results in `http://localhost:3000/bar.html` + * - baseURL: `http://localhost:3000/foo/` and navigating to `./bar.html` results in `http://localhost:3000/foo/bar.html` */ baseURL: string | undefined; - /** - * Options used to create the context. Other options above (e.g. `viewport`) take priority. - * @see BrowserContextOptions + * Options used to create the context, as passed to + * [browser.newContext([options])](https://playwright.dev/docs/api/class-browser#browser-new-context). Specific options + * like [fixtures.viewport](https://playwright.dev/docs/api/class-fixtures#fixtures-viewport) take priority over this. */ contextOptions: BrowserContextOptions; -}; +} /** - * Arguments available to the test function and all hooks (beforeEach, afterEach, beforeAll, afterAll). + * Playwright Test is based on the concept of the [test fixtures](https://playwright.dev/docs/test-fixtures). Test fixtures are used to establish + * environment for each test, giving the test everything it needs and nothing else. + * + * Playwright Test looks at each test declaration, analyses the set of fixtures the test needs and prepares those fixtures + * specifically for the test. Values prepared by the fixtures are merged into a single object that is available to the + * `test`, hooks, annotations and other fixtures as a first parameter. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * Given the test above, Playwright Test will set up the `page` fixture before running the test, and tear it down after the + * test has finished. `page` fixture provides a [Page] object that is available to the test. + * + * Playwright Test comes with builtin fixtures listed below, and you can add your own fixtures as well. Many fixtures are + * designed as "options" that you can set in your + * [testConfig.use](https://playwright.dev/docs/api/class-testconfig#test-config-use) section. + * + * ```js js-flavor=js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * const config: PlaywrightTestConfig = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * export default config; + * ``` + * + * Alternatively, with [test.use(fixtures)](https://playwright.dev/docs/api/class-test#test-use) you can override some + * options for a file. + * + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * */ -export type PlaywrightWorkerArgs = { - /** - * The Playwright instance. - */ +export interface PlaywrightWorkerArgs { playwright: typeof import('..'); - - /** - * Browser instance, shared between multiple tests. - */ browser: Browser; -}; +} /** - * Arguments available to the test function and beforeEach/afterEach hooks. + * Playwright Test is based on the concept of the [test fixtures](https://playwright.dev/docs/test-fixtures). Test fixtures are used to establish + * environment for each test, giving the test everything it needs and nothing else. + * + * Playwright Test looks at each test declaration, analyses the set of fixtures the test needs and prepares those fixtures + * specifically for the test. Values prepared by the fixtures are merged into a single object that is available to the + * `test`, hooks, annotations and other fixtures as a first parameter. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * // ... + * }); + * ``` + * + * Given the test above, Playwright Test will set up the `page` fixture before running the test, and tear it down after the + * test has finished. `page` fixture provides a [Page] object that is available to the test. + * + * Playwright Test comes with builtin fixtures listed below, and you can add your own fixtures as well. Many fixtures are + * designed as "options" that you can set in your + * [testConfig.use](https://playwright.dev/docs/api/class-testconfig#test-config-use) section. + * + * ```js js-flavor=js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * const config: PlaywrightTestConfig = { + * use: { + * headless: false, + * viewport: { width: 1280, height: 720 }, + * ignoreHTTPSErrors: true, + * video: 'on-first-retry', + * }, + * }; + * export default config; + * ``` + * + * Alternatively, with [test.use(fixtures)](https://playwright.dev/docs/api/class-test#test-use) you can override some + * options for a file. + * + * ```js js-flavor=js + * // example.spec.js + * const { test, expect } = require('@playwright/test'); + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * // example.spec.ts + * import { test, expect } from '@playwright/test'; + * + * // Run tests in this file with portrait-like viewport. + * test.use({ viewport: { width: 600, height: 900 } }); + * + * test('my portrait test', async ({ page }) => { + * // ... + * }); + * ``` + * */ -export type PlaywrightTestArgs = { +export interface PlaywrightTestArgs { /** - * BrowserContext instance, created fresh for each test. + * Isolated [BrowserContext] instance, created for each test. Since contexts are isolated between each other, every test + * gets a fresh environment, even when multiple tests run in a single [Browser] for maximum efficiency. + * + * Learn how to [configure context](https://playwright.dev/docs/test-configuration) through other fixtures and options. + * + * The [fixtures.page](https://playwright.dev/docs/api/class-fixtures#fixtures-page) belongs to this context. */ context: BrowserContext; - /** - * Page instance, created fresh for each test. + * Isolated [Page] instance, created for each test. Pages are isolated between tests due to + * [fixtures.context](https://playwright.dev/docs/api/class-fixtures#fixtures-context) isolation. + * + * This is the most common fixture used in a test. + * + * ```js js-flavor=js + * const { test, expect } = require('@playwright/test'); + * + * test('basic test', async ({ page }) => { + * await page.goto('/signin'); + * await page.fill('#username', 'User'); + * await page.fill('#password', 'pwd'); + * await page.click('text=Sign in'); + * // ... + * }); + * ``` + * + * ```js js-flavor=ts + * import { test, expect } from '@playwright/test'; + * + * test('basic test', async ({ page }) => { + * await page.goto('/signin'); + * await page.fill('#username', 'User'); + * await page.fill('#password', 'pwd'); + * await page.click('text=Sign in'); + * // ... + * }); + * ``` + * */ page: Page; -}; +} export type PlaywrightTestProject = Project; export type PlaywrightTestConfig = Config; @@ -1248,3 +2733,7 @@ export const expect: Expect; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; + + + + diff --git a/types/testReporter.d.ts b/types/testReporter.d.ts index c408eb37da..1ea829ef99 100644 --- a/types/testReporter.d.ts +++ b/types/testReporter.d.ts @@ -1,3 +1,4 @@ +// This file is generated by /utils/generate_types/index.js /** * Copyright (c) Microsoft Corporation. * @@ -18,187 +19,195 @@ import type { FullConfig, TestStatus, TestError } from './test'; export type { FullConfig, TestStatus, TestError } from './test'; /** - * Location where TestCase or Suite was defined. + * Represents a location in the source code where [TestCase] or [Suite] is defined. */ export interface Location { /** - * Path to the file. + * Path to the source file. */ file: string; - /** - * Line number in the file. + * Line number in the source file. */ line: number; - /** - * Column number in the file. + * Column number in the source file. */ column: number; } /** - * A group of tests. All tests are reported in the following hierarchy: - * - Root suite - * - Project suite #1 (for each project) - * - File suite #1 (for each file in the project) - * - Suites for any describe() calls - * - TestCase #1 defined in the file or describe() group - * - TestCase #2 - * ... < more test cases > + * `Suite` is a group of tests. All tests in Playwright Test form the following hierarchy: + * - Root suite has a child suite for each [TestProject]. + * - Project suite #1. Has a child suite for each test file in the project. + * - File suite #1 + * - [TestCase] #1 + * - [TestCase] #2 + * - Suite corresponding to a + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) group + * - [TestCase] #1 in a group + * - [TestCase] #2 in a group + * - < more test cases ... > * - File suite #2 - * ... < more file suites > - * - Second project suite - * ... < more project suites > + * - < more file suites ... > + * - Project suite #2 + * - < more project suites ... > + * + * Reporter is given a root suite in the + * [reporter.onBegin(config, suite)](https://playwright.dev/docs/api/class-reporter#reporter-on-begin) method. */ export interface Suite { /** - * Suite title: - * - Empty for root suite. - * - Project name for project suite. - * - File path for file suite. - * - Title passed to describe() for describe suites. + * Suite title. + * - Empty for root suite. + * - Project name for project suite. + * - File path for file suite. + * - Title passed to [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) for a + * group suite. */ title: string; - /** - * Location where the suite is defined. + * Location in the source where the suite is defined. Missing for root and project suites. */ location?: Location; - /** - * Child suites. + * Child suites. See [Suite] for the hierarchy of suites. */ suites: Suite[]; - /** - * Test cases in the suite. Note that only test cases defined directly in this suite - * are in the list. Any test cases defined in nested describe() groups are listed - * in the child `suites`. + * Test cases in the suite. Note that only test cases defined directly in this suite are in the list. Any test cases + * defined in nested [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups are + * listed in the child [suite.suites](https://playwright.dev/docs/api/class-suite#suite-suites). */ tests: TestCase[]; - /** - * A list of titles from the root down to this suite. + * Returns a list of titles from the root down to this suite. */ titlePath(): string[]; - /** - * Returns the list of all test cases in this suite and its descendants. + * Returns the list of all test cases in this suite and its descendants, as opposite to + * [suite.tests](https://playwright.dev/docs/api/class-suite#suite-tests). */ allTests(): TestCase[]; } /** - * `TestCase` corresponds to a test() call in a test file. When a single test() is - * running in multiple projects or repeated multiple times, it will have multiple - * `TestCase` objects in corresponding projects' suites. + * `TestCase` corresponds to every [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call) + * call in a test file. When a single + * [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call) is running in multiple projects + * or repeated multiple times, it will have multiple `TestCase` objects in corresponding projects' suites. */ export interface TestCase { /** - * Test title as passed to the test() call. + * Test title as passed to the [test.(call)(title, testFunction)](https://playwright.dev/docs/api/class-test#test-call) + * call. */ title: string; - /** - * Location where the test is defined. + * Location in the source where the test is defined. */ location: Location; - /** - * A list of titles from the root down to this test. + * Returns a list of titles from the root down to this test. */ titlePath(): string[]; - /** - * Expected status. - * - Tests marked as test.skip() or test.fixme() are expected to be 'skipped'. - * - Tests marked as test.fail() are expected to be 'failed'. - * - Other tests are expected to be 'passed'. + * Expected test status. + * - Tests marked as [test.skip([condition, description])](https://playwright.dev/docs/api/class-test#test-skip) or + * [test.fixme([condition, description])](https://playwright.dev/docs/api/class-test#test-fixme) are expected to be + * `'skipped'`. + * - Tests marked as [test.fail([condition, description])](https://playwright.dev/docs/api/class-test#test-fail) are + * expected to be `'failed'`. + * - Other tests are expected to be `'passed'`. + * + * See also [testResult.status](https://playwright.dev/docs/api/class-testresult#test-result-status) for the actual status. */ expectedStatus: TestStatus; - /** - * The timeout given to the test. Affected by timeout in the configuration file, - * and calls to test.setTimeout() or test.slow(). + * The timeout given to the test. Affected by + * [testConfig.timeout](https://playwright.dev/docs/api/class-testconfig#test-config-timeout), + * [testProject.timeout](https://playwright.dev/docs/api/class-testproject#test-project-timeout), + * [test.setTimeout(timeout)](https://playwright.dev/docs/api/class-test#test-set-timeout), + * [test.slow([condition, description])](https://playwright.dev/docs/api/class-test#test-slow) and + * [testInfo.setTimeout(timeout)](https://playwright.dev/docs/api/class-testinfo#test-info-set-timeout). */ timeout: number; - /** - * Annotations collected for this test. For example, calling - * `test.skip(true, 'just because')` will produce an annotation - * `{ type: 'skip', description: 'just because' }`. + * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all + * [test.describe(title, callback)](https://playwright.dev/docs/api/class-test#test-describe) groups the test belongs to + * and file-level annotations for the test file. + * + * Annotations are available during test execution through + * [testInfo.annotations](https://playwright.dev/docs/api/class-testinfo#test-info-annotations). + * + * Learn more about [test annotations](https://playwright.dev/docs/test-annotations). */ annotations: { type: string, description?: string }[]; - /** - * The maxmium number of retries given to this test in the configuration. + * The maximum number of retries given to this test in the configuration. + * + * Learn more about [test retries](https://playwright.dev/docs/test-retries). */ retries: number; - /** * Results for each run of this test. */ results: TestResult[]; - /** - * Testing outcome for this test. Note that outcome does not directly match to the status: - * - Test that is expected to fail and actually fails is 'expected'. - * - Test that passes on a second retry is 'flaky'. + * Testing outcome for this test. Note that outcome is not the same as + * [testResult.status](https://playwright.dev/docs/api/class-testresult#test-result-status): + * - Test that is expected to fail and actually fails is `'expected'`. + * - Test that passes on a second retry is `'flaky'`. */ outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky'; - /** - * Whether the test is considered running fine. - * Non-ok tests fail the test run with non-zero exit code. + * Whether the test is considered running fine. Non-ok tests fail the test run with non-zero exit code. */ ok(): boolean; } /** - * A result of a single test run. + * A result of a single [TestCase] run. */ export interface TestResult { /** * When test is retries multiple times, each retry attempt is given a sequential number. + * + * Learn more about [test retries](https://playwright.dev/docs/test-retries). */ retry: number; - /** * Index of the worker where the test was run. + * + * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test. */ workerIndex: number; - /** - * Test run start time. + * Start time of this particular test run. */ startTime: Date; - /** * Running time in milliseconds. */ duration: number; - /** - * The status of this test result. + * The status of this test result. See also + * [testCase.expectedStatus](https://playwright.dev/docs/api/class-testcase#test-case-expected-status). */ status?: TestStatus; - /** - * An error from this test result, if any. + * An error thrown during the test execution, if any. */ error?: TestError; - /** - * Any attachments created during the test run. + * The list of files or buffers attached during the test execution through + * [testInfo.attachments](https://playwright.dev/docs/api/class-testinfo#test-info-attachments). */ attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; - /** * Anything written to the standard output during the test run. */ stdout: (string | Buffer)[]; - /** * Anything written to the standard error during the test run. */ @@ -220,47 +229,138 @@ export interface FullResult { } /** - * Test runner notifies reporter about various events during the test run. + * Test runner notifies the reporter about various events during test execution. All methods of the reporter are optional. + * + * You can create a custom reporter my implementing a class with some of the reporter methods. Make sure to export this + * class as default. + * + * ```js js-flavor=js + * // my-awesome-reporter.js + * // @ts-check + * + * /** @implements {import('@playwright/test/reporter').Reporter} *\/ + * class MyReporter { + * onBegin(config, suite) { + * console.log(`Starting the run with ${suite.allTests().length} tests`); + * } + * + * onTestBegin(test) { + * console.log(`Starting test ${test.title}`); + * } + * + * onTestEnd(test, result) { + * console.log(`Finished test ${test.title}: ${result.status}`); + * } + * + * onEnd(result) { + * console.log(`Finished the run: ${result.status}`); + * } + * } + * + * module.exports = MyReporter; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { Reporter } from '@playwright/test/reporter'; + * + * class MyReporter implements Reporter { + * onBegin(config, suite) { + * console.log(`Starting the run with ${suite.allTests().length} tests`); + * } + * + * onTestBegin(test) { + * console.log(`Starting test ${test.title}`); + * } + * + * onTestEnd(test, result) { + * console.log(`Finished test ${test.title}: ${result.status}`); + * } + * + * onEnd(result) { + * console.log(`Finished the run: ${result.status}`); + * } + * } + * export default MyReporter; + * ``` + * + * Now use this reporter with [testConfig.reporter](https://playwright.dev/docs/api/class-testconfig#test-config-reporter). + * + * ```js js-flavor=js + * // playwright.config.js + * // @ts-check + * + * /** @type {import('@playwright/test').PlaywrightTestConfig} *\/ + * const config = { + * reporter: './my-awesome-reporter.js', + * }; + * + * module.exports = config; + * ``` + * + * ```js js-flavor=ts + * // playwright.config.ts + * import { PlaywrightTestConfig } from '@playwright/test'; + * + * const config: PlaywrightTestConfig = { + * reporter: './my-awesome-reporter.ts', + * }; + * export default config; + * ``` + * + * Learn more about [reporters](https://playwright.dev/docs/test-reporters). */ export interface Reporter { /** - * Called once before running tests. - * All tests have been already discovered and put into a hierarchy, see `Suite` description. + * Called once before running tests. All tests have been already discovered and put into a hierarchy of [Suite]s. + * @param config Resolved configuration. + * @param suite The root suite that contains all projects, files and test cases. */ onBegin?(config: FullConfig, suite: Suite): void; - /** * Called after a test has been started in the worker process. + * @param test Test that has been started. */ onTestBegin?(test: TestCase): void; - /** * Called when something has been written to the standard output in the worker process. - * When `test` is given, output happened while the test was running. + * @param chunk Output chunk. + * @param test Test that was running. Note that output may happen when to test is running, in which case this will be [void]. */ onStdOut?(chunk: string | Buffer, test?: TestCase): void; - /** * Called when something has been written to the standard error in the worker process. - * When `test` is given, output happened while the test was running. + * @param chunk Output chunk. + * @param test Test that was running. Note that output may happen when to test is running, in which case this will be [void]. */ onStdErr?(chunk: string | Buffer, test?: TestCase): void; - /** * Called after a test has been finished in the worker process. + * @param test Test that has been finished. + * @param result Result of the test run. */ onTestEnd?(test: TestCase, result: TestResult): void; - /** - * Called on some global error, for example unhandled expection in the worker process. + * Called on some global error, for example unhandled exception in the worker process. + * @param error The error. */ onError?(error: TestError): void; - /** - * Called after all tests has been run, or when testing has been interrupted. + * Called after all tests has been run, or testing has been interrupted. Note that this method may return a [Promise] and + * Playwright Test will await it. + * @param result Result of the full test run. - `'passed'` - Everything went as expected. + * - `'failed'` - Any test has failed. + * - `'timedout'` - The + * [testConfig.globalTimeout](https://playwright.dev/docs/api/class-testconfig#test-config-global-timeout) has been + * reached. + * - `'interrupted'` - Interrupted by the user. */ onEnd?(result: FullResult): void | Promise; } // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; + + + + diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 0b8d76c702..13cf5fe638 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -26,419 +26,482 @@ const {parseOverrides} = require('./parseOverrides'); const exported = require('./exported.json'); const { parseApi } = require('../doclint/api_parser'); -const objectDefinitions = []; -const handledMethods = new Set(); -/** @type {Documentation} */ -let documentation; -let hadChanges = false; +/** @typedef {import('../doclint/documentation').Member} Member */ + +Error.stackTraceLimit = 50; + +class TypesGenerator { + /** + * @param {Documentation} documentation + */ + constructor(documentation) { + /** @type {Array<{name: string, properties: Member[]}>} */ + this.objectDefinitions = []; + /** @type {Set} */ + this.handledMethods = new Set(); + this.documentation = documentation; + } + + /** + * @param {string} overridesFile + * @param {Map=} docsOnlyClassMapping + * @returns {Promise} + */ + async generateTypes(overridesFile, docsOnlyClassMapping) { + this.documentation.filterForLanguage('js'); + this.documentation.copyDocsFromSuperclasses([]); + + const createMarkdownLink = (member, text) => { + const className = toKebabCase(member.clazz.name); + const memberName = toKebabCase(member.name); + let hash = null + if (member.kind === 'property' || member.kind === 'method') + hash = `${className}-${memberName}`.toLowerCase(); + else if (member.kind === 'event') + hash = `${className}-event-${memberName}`.toLowerCase(); + return `[${text}](https://playwright.dev/docs/api/class-${member.clazz.name.toLowerCase()}#${hash})`; + }; + this.documentation.setLinkRenderer(item => { + const { clazz, member, param, option } = item; + if (param) + return `\`${param}\``; + if (option) + return `\`${option}\``; + if (clazz) + return `[${clazz.name}]`; + if (member.kind === 'method') + return createMarkdownLink(member, `${member.clazz.varName}.${member.alias}(${this.renderJSSignature(member.argsArray)})`); + if (member.kind === 'event') + return createMarkdownLink(member, `${member.clazz.varName}.on('${member.alias.toLowerCase()}')`); + if (member.kind === 'property') + return createMarkdownLink(member, `${member.clazz.varName}.${member.alias}`); + throw new Error('Unknown member kind ' + member.kind); + }); + this.documentation.generateSourceCodeComments(); + + const handledClasses = new Set(); + + const overrides = await parseOverrides(overridesFile, className => { + const docClass = this.docClassForName(className, docsOnlyClassMapping); + if (!docClass) + return ''; + handledClasses.add(className); + return this.writeComment(docClass.comment) + '\n'; + }, (className, methodName) => { + const docClass = this.docClassForName(className, docsOnlyClassMapping); + const method = docClass ? docClass.membersArray.find(m => m.alias === methodName) : undefined; + if (docsOnlyClassMapping && !method) + return ''; + this.handledMethods.add(`${className}.${methodName}`); + if (!method) { + if (new Set(['on', 'addListener', 'off', 'removeListener', 'once']).has(methodName)) + return ''; + throw new Error(`Unknown override method "${className}.${methodName}"`); + } + return this.memberJSDOC(method, ' ').trimLeft(); + }, (className) => { + const docClass = this.docClassForName(className, docsOnlyClassMapping); + return (!docsOnlyClassMapping && docClass) ? this.classBody(docClass) : ''; + }); + + const classes = this.documentation.classesArray.filter(cls => !handledClasses.has(cls.name)); + return [ + `// This file is generated by ${__filename.substring(path.join(__dirname, '..', '..').length).split(path.sep).join(path.posix.sep)}`, + overrides, + '', + docsOnlyClassMapping ? '' : classes.map(classDesc => this.classToString(classDesc)).join('\n'), + this.objectDefinitionsToString(overrides), + '', + ].join('\n'); + } + + /** + * @param {string} name + * @param {Map | undefined} docsOnlyClassMapping + */ + docClassForName(name, docsOnlyClassMapping) { + name = (docsOnlyClassMapping ? docsOnlyClassMapping.get(name) : undefined) || name; + const docClass = this.documentation.classes.get(name); + if (!docClass && !docsOnlyClassMapping) + throw new Error(`Unknown override class ${name}`); + return docClass; + } + + /** + * @param {string} overriddes + */ + objectDefinitionsToString(overriddes) { + let definition; + const parts = []; + const internalWords = new Set(overriddes.split(/[^\w$]/g)); + while ((definition = this.objectDefinitions.pop())) { + const {name, properties} = definition; + const shouldExport = !!exported[name]; + const usedInternally = internalWords.has(name); + if (!usedInternally && !shouldExport) + continue; + parts.push(`${shouldExport ? 'export ' : ''}interface ${name} ${this.stringifyObjectType(properties, name, '')}\n`) + } + return parts.join('\n'); + } + + nameForProperty(member) { + return (member.required || member.alias.startsWith('...')) ? member.alias : member.alias + '?'; + } + + /** + * @param {Documentation.Class} classDesc + */ + classToString(classDesc) { + const parts = []; + if (classDesc.comment) { + parts.push(this.writeComment(classDesc.comment)) + } + parts.push(`export interface ${classDesc.name} ${classDesc.extends ? `extends ${classDesc.extends} ` : ''}{`); + parts.push(this.classBody(classDesc)); + parts.push('}\n'); + return parts.join('\n'); + } + + /** + * @param {string} type + */ + argNameForType(type) { + if (type === 'void') + return null; + if (type.includes('{')) + return 'data'; + return (type[0].toLowerCase() + type.slice(1)).replace(/\|/g, 'Or'); + } + + /** + * @param {Documentation.Class} classDesc + */ + hasUniqueEvents(classDesc) { + if (!classDesc.events.size) + return false; + const parent = this.parentClass(classDesc); + if (!parent) + return true; + return Array.from(classDesc.events.keys()).some(eventName => !parent.events.has(eventName)); + } + + /** + * @param {Documentation.Class} classDesc + */ + createEventDescriptions(classDesc) { + if (!this.hasUniqueEvents(classDesc)) + return []; + const descriptions = []; + for (let [eventName, value] of classDesc.events) { + eventName = eventName.toLowerCase(); + const type = this.stringifyComplexType(value && value.type, '', classDesc.name, eventName, 'payload'); + const argName = this.argNameForType(type); + const params = argName ? `${argName}: ${type}` : ''; + descriptions.push({ + type, + params, + eventName, + comment: value.comment + }); + } + return descriptions; + } + + /** + * @param {Documentation.Class} classDesc + */ + classBody(classDesc) { + const parts = []; + const eventDescriptions = this.createEventDescriptions(classDesc); + const commentForMethod = { + off: 'Removes an event listener added by `on` or `addListener`.', + removeListener: 'Removes an event listener added by `on` or `addListener`.', + once: 'Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.' + } + for (const method of ['on', 'once', 'addListener', 'removeListener', 'off']) { + for (const {eventName, params, comment} of eventDescriptions) { + if ((method === 'on' || method === 'addListener') && comment) + parts.push(this.writeComment(comment, ' ')); + else + parts.push(this.writeComment(commentForMethod[method], ' ')); + parts.push(` ${method}(event: '${eventName}', listener: (${params}) => void): this;\n`); + } + } + + const members = classDesc.membersArray.filter(member => member.kind !== 'event'); + parts.push(members.map(member => { + if (member.kind === 'event') + return ''; + if (member.alias === 'waitForEvent') { + const parts = []; + for (const {eventName, params, comment, type} of eventDescriptions) { + if (comment) + parts.push(this.writeComment(comment, ' ')); + parts.push(` ${member.alias}(event: '${eventName}', optionsOrPredicate?: { predicate?: (${params}) => boolean | Promise, timeout?: number } | ((${params}) => boolean | Promise)): Promise<${type}>;\n`); + } + + return parts.join('\n'); + } + const jsdoc = this.memberJSDOC(member, ' '); + const args = this.argsFromMember(member, ' ', classDesc.name); + let type = this.stringifyComplexType(member.type, ' ', classDesc.name, member.alias); + if (member.async) + type = `Promise<${type}>`; + // do this late, because we still want object definitions for overridden types + if (!this.hasOwnMethod(classDesc, member.alias)) + return ''; + return `${jsdoc}${member.alias}${args}: ${type};` + }).filter(x => x).join('\n\n')); + return parts.join('\n'); + } + + /** + * @param {Documentation.Class} classDesc + * @param {string} methodName + */ + hasOwnMethod(classDesc, methodName) { + if (this.handledMethods.has(`${classDesc.name}.${methodName}`)) + return false; + while (classDesc = this.parentClass(classDesc)) { + if (classDesc.members.has(methodName)) + return false; + } + return true; + } + + /** + * @param {Documentation.Class} classDesc + */ + parentClass(classDesc) { + if (!classDesc.extends) + return null; + return this.documentation.classes.get(classDesc.extends); + } + + writeComment(comment, indent = '') { + const parts = []; + const out = []; + const pushLine = (line) => { + if (line || out[out.length - 1]) + out.push(line) + }; + let skipExample = false; + for (let line of comment.split('\n')) { + const match = line.match(/```(\w+)/); + if (match) { + const lang = match[1]; + skipExample = !["html", "yml", "bash", "js"].includes(lang); + } else if (skipExample && line.trim().startsWith('```')) { + skipExample = false; + continue; + } + if (!skipExample) + pushLine(line); + } + comment = out.join('\n'); + comment = comment.replace(/\[([^\]]+)\]\(\.\/([^\)]+)\)/g, (match, p1, p2) => { + return `[${p1}](https://playwright.dev/docs/${p2.replace('.md', '')})`; + }); + + parts.push(indent + '/**'); + parts.push(...comment.split('\n').map(line => indent + ' * ' + line.replace(/\*\//g, '*\\/'))); + parts.push(indent + ' */'); + return parts.join('\n'); + } + + /** + * @param {Documentation.Type} type + */ + stringifyComplexType(type, indent, ...namespace) { + if (!type) + return 'void'; + return this.stringifySimpleType(type, indent, ...namespace); + } + + stringifyObjectType(properties, name, indent = '') { + const parts = []; + parts.push(`{`); + parts.push(properties.map(member => `${this.memberJSDOC(member, indent + ' ')}${this.nameForProperty(member)}${this.argsFromMember(member, indent + ' ', name)}: ${this.stringifyComplexType(member.type, indent + ' ', name, member.name)};`).join('\n\n')); + parts.push(indent + '}'); + return parts.join('\n'); + } + + /** + * @param {Documentation.Type=} type + * @returns{string} + */ + stringifySimpleType(type, indent = '', ...namespace) { + if (!type) + return 'void'; + if (type.name === 'Object' && type.templates) { + const keyType = this.stringifySimpleType(type.templates[0], indent, ...namespace); + const valueType = this.stringifySimpleType(type.templates[1], indent, ...namespace); + return `{ [key: ${keyType}]: ${valueType}; }`; + } + let out = type.name; + if (out === 'int' || out === 'float') + out = 'number'; + + if (type.name === 'Object' && type.properties && type.properties.length) { + const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join(''); + const shouldExport = exported[name]; + const properties = namespace[namespace.length -1] === 'options' ? type.sortedProperties() : type.properties; + this.objectDefinitions.push({name, properties}); + if (shouldExport) { + out = name; + } else { + out = this.stringifyObjectType(properties, name, indent); + } + } + + if (type.args) { + const stringArgs = type.args.map(a => ({ + type: this.stringifySimpleType(a, indent, ...namespace), + name: a.name.toLowerCase() + })); + out = `((${stringArgs.map(({name, type}) => `${name}: ${type}`).join(', ')}) => ${this.stringifySimpleType(type.returnType, indent, ...namespace)})`; + } else if (type.name === 'function') { + out = 'Function'; + } + if (out === 'path') + return 'string'; + if (out === 'Any') + return 'any'; + if (type.templates) + out += '<' + type.templates.map(t => this.stringifySimpleType(t, indent, ...namespace)).join(', ') + '>'; + if (type.union) + out = type.union.map(t => this.stringifySimpleType(t, indent, ...namespace)).join('|'); + return out.trim(); + } + + /** + * @param {Documentation.Member} member + */ + argsFromMember(member, indent, ...namespace) { + if (member.kind === 'property') + return ''; + return '(' + member.argsArray.map(arg => `${this.nameForProperty(arg)}: ${this.stringifyComplexType(arg.type, indent, ...namespace, member.name, arg.name)}`).join(', ') + ')'; + } + + /** + * @param {Documentation.Member} member + * @param {string} indent + */ + memberJSDOC(member, indent) { + const lines = []; + if (member.comment) + lines.push(...member.comment.split('\n')); + if (member.deprecated) + lines.push('@deprecated'); + lines.push(...member.argsArray.map(arg => `@param ${arg.alias.replace(/\./g, '')} ${arg.comment.replace('\n', ' ')}`)); + if (!lines.length) + return indent; + return this.writeComment(lines.join('\n'), indent) + '\n' + indent; + } + + /** + * @param {Documentation.Member[]} args + */ + renderJSSignature(args) { + const tokens = []; + let hasOptional = false; + for (const arg of args) { + const name = arg.alias; + const optional = !arg.required; + if (tokens.length) { + if (optional && !hasOptional) + tokens.push(`[, ${name}`); + else + tokens.push(`, ${name}`); + } else { + if (optional && !hasOptional) + tokens.push(`[${name}`); + else + tokens.push(`${name}`); + } + hasOptional = hasOptional || optional; + } + if (hasOptional) + tokens.push(']'); + return tokens.join(''); + } +} (async function() { + let hadChanges = false; + + /** + * @param {string} filePath + * @param {string} content + */ + function writeFile(filePath, content) { + if (os.platform() === 'win32') + content = content.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); + const existing = fs.readFileSync(filePath, 'utf8'); + if (existing === content) + return; + hadChanges = true; + console.error(`Writing //${path.relative(PROJECT_DIR, filePath)}`); + fs.writeFileSync(filePath, content, 'utf8'); + } + const typesDir = path.join(PROJECT_DIR, 'types'); if (!fs.existsSync(typesDir)) fs.mkdirSync(typesDir) writeFile(path.join(typesDir, 'protocol.d.ts'), fs.readFileSync(path.join(PROJECT_DIR, 'src', 'server', 'chromium', 'protocol.d.ts'), 'utf8')); - documentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api')); - documentation.filterForLanguage('js'); - documentation.copyDocsFromSuperclasses([]); - const createMarkdownLink = (member, text) => { - const className = toKebabCase(member.clazz.name); - const memberName = toKebabCase(member.name); - let hash = null - if (member.kind === 'property' || member.kind === 'method') - hash = `${className}-${memberName}`.toLowerCase(); - else if (member.kind === 'event') - hash = `${className}-event-${memberName}`.toLowerCase(); - return `[${text}](https://playwright.dev/docs/api/class-${member.clazz.name.toLowerCase()}#${hash})`; - }; - documentation.setLinkRenderer(item => { - const { clazz, member, param, option } = item; - if (param) - return `\`${param}\``; - if (option) - return `\`${option}\``; - if (clazz) - return `[${clazz.name}]`; - if (member.kind === 'method') - return createMarkdownLink(member, `${member.clazz.varName}.${member.alias}(${renderJSSignature(member.argsArray)})`); - if (member.kind === 'event') - return createMarkdownLink(member, `${member.clazz.varName}.on('${member.alias.toLowerCase()}')`); - if (member.kind === 'property') - return createMarkdownLink(member, `${member.clazz.varName}.${member.alias}`); - throw new Error('Unknown member kind ' + member.kind); - }); - documentation.generateSourceCodeComments(); + const apiDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'api')); // Root module types are overridden. - const playwrightClass = documentation.classes.get('Playwright'); - documentation.classes.delete('Playwright'); - documentation.classesArray.splice(documentation.classesArray.indexOf(playwrightClass), 1); - - const handledClasses = new Set(); - - function docClassForName(name) { - const docClass = documentation.classes.get(name); - if (!docClass) - throw new Error(`Unknown override class "${name}"`); - return docClass; - } - const overrides = await parseOverrides(className => { - handledClasses.add(className); - return writeComment(docClassForName(className).comment) + '\n'; - }, (className, methodName) => { - const docClass = docClassForName(className); - const method = docClass.methodsArray.find(m => m.alias === methodName); - handledMethods.add(`${className}.${methodName}`); - if (!method) { - if (new Set(['on', 'addListener', 'off', 'removeListener', 'once']).has(methodName)) - return ''; - throw new Error(`Unknown override method "${className}.${methodName}"`); - } - return memberJSDOC(method, ' ').trimLeft(); - }, (className) => { - return classBody(docClassForName(className)); - }); - const classes = documentation.classesArray.filter(cls => !handledClasses.has(cls.name)); - let output = `// This file is generated by ${__filename.substring(path.join(__dirname, '..', '..').length).split(path.sep).join(path.posix.sep)} -${overrides} - -${classes.map(classDesc => classToString(classDesc)).join('\n')} -${objectDefinitionsToString(overrides)} -${generateDevicesTypes()} - -export interface ChromiumBrowserContext extends BrowserContext { } -export interface ChromiumBrowser extends Browser { } -export interface FirefoxBrowser extends Browser { } -export interface WebKitBrowser extends Browser { } -export interface ChromiumCoverage extends Coverage { } -`; + apiDocumentation.classesArray = apiDocumentation.classesArray.filter(cls => cls.name !== 'Playwright'); + apiDocumentation.index(); + const apiTypesGenerator = new TypesGenerator(apiDocumentation); + let apiTypes = await apiTypesGenerator.generateTypes(path.join(__dirname, 'overrides.d.ts')); + const namedDevices = Object.keys(devices).map(name => ` ${JSON.stringify(name)}: DeviceDescriptor;`).join('\n'); + apiTypes += [ + `type Devices = {`, + namedDevices, + ` [key: string]: DeviceDescriptor;`, + `}`, + ``, + `export interface ChromiumBrowserContext extends BrowserContext { }`, + `export interface ChromiumBrowser extends Browser { }`, + `export interface FirefoxBrowser extends Browser { }`, + `export interface WebKitBrowser extends Browser { }`, + `export interface ChromiumCoverage extends Coverage { }`, + ``, + ].join('\n'); for (const [key, value] of Object.entries(exported)) - output = output.replace(new RegExp('\\b' + key + '\\b', 'g'), value); - // remove trailing whitespace - output = output.replace(/( +)\n/g, '\n'); - writeFile(path.join(typesDir, 'types.d.ts'), output); + apiTypes = apiTypes.replace(new RegExp('\\b' + key + '\\b', 'g'), value); + apiTypes = apiTypes.replace(/( +)\n/g, '\n'); // remove trailing whitespace + writeFile(path.join(typesDir, 'types.d.ts'), apiTypes); + + const testOnlyDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'test-api'), path.join(PROJECT_DIR, 'docs', 'src', 'api', 'params.md')); + const testDocumentation = apiDocumentation.mergeWith(testOnlyDocumentation); + const testTypesGenerator = new TypesGenerator(testDocumentation); + const testClassMapping = new Map([ + ['TestType', 'Test'], + ['Config', 'TestConfig'], + ['FullConfig', 'TestConfig'], + ['Project', 'TestProject'], + ['PlaywrightWorkerOptions', 'Fixtures'], + ['PlaywrightTestOptions', 'Fixtures'], + ['PlaywrightWorkerArgs', 'Fixtures'], + ['PlaywrightTestArgs', 'Fixtures'], + ]); + let testTypes = await testTypesGenerator.generateTypes(path.join(__dirname, 'overrides-test.d.ts'), testClassMapping); + testTypes = testTypes.replace(/( +)\n/g, '\n'); // remove trailing whitespace + writeFile(path.join(typesDir, 'test.d.ts'), testTypes); + + const testReporterOnlyDocumentation = parseApi(path.join(PROJECT_DIR, 'docs', 'src', 'test-reporter-api')); + const testReporterDocumentation = testDocumentation.mergeWith(testReporterOnlyDocumentation); + const testReporterTypesGenerator = new TypesGenerator(testReporterDocumentation); + let testReporterTypes = await testReporterTypesGenerator.generateTypes(path.join(__dirname, 'overrides-testReporter.d.ts'), new Map()); + testReporterTypes = testReporterTypes.replace(/( +)\n/g, '\n'); // remove trailing whitespace + writeFile(path.join(typesDir, 'testReporter.d.ts'), testReporterTypes); + process.exit(hadChanges && process.argv.includes('--check-clean') ? 1 : 0); })().catch(e => { console.error(e); process.exit(1); }); - -function writeFile(filePath, content) { - if (os.platform() === 'win32') - content = content.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); - const existing = fs.readFileSync(filePath, 'utf8'); - if (existing === content) - return; - hadChanges = true; - console.error(`Writing //${path.relative(PROJECT_DIR, filePath)}`); - fs.writeFileSync(filePath, content, 'utf8'); -} - -/** - * @param {string} overriddes - */ -function objectDefinitionsToString(overriddes) { - let definition; - const parts = []; - const internalWords = new Set(overriddes.split(/[^\w$]/g)); - while ((definition = objectDefinitions.pop())) { - const {name, properties} = definition; - const shouldExport = !!exported[name]; - const usedInternally = internalWords.has(name); - if (!usedInternally && !shouldExport) - continue; - parts.push(`${shouldExport ? 'export ' : ''}interface ${name} ${stringifyObjectType(properties, name, '')}\n`) - } - return parts.join('\n'); -} - -function nameForProperty(member) { - return (member.required || member.alias.startsWith('...')) ? member.alias : member.alias + '?'; -} - -/** - * @param {Documentation.Class} classDesc - */ -function classToString(classDesc) { - const parts = []; - if (classDesc.comment) { - parts.push(writeComment(classDesc.comment)) - } - parts.push(`export interface ${classDesc.name} ${classDesc.extends ? `extends ${classDesc.extends} ` : ''}{`); - parts.push(classBody(classDesc)); - parts.push('}\n'); - return parts.join('\n'); -} - -/** - * @param {string} type - */ -function argNameForType(type) { - if (type === 'void') - return null; - if (type.includes('{')) - return 'data'; - return (type[0].toLowerCase() + type.slice(1)).replace(/\|/g, 'Or'); -} - -/** - * @param {Documentation.Class} classDesc - */ -function hasUniqueEvents(classDesc) { - if (!classDesc.events.size) - return false; - const parent = parentClass(classDesc); - if (!parent) - return true; - return Array.from(classDesc.events.keys()).some(eventName => !parent.events.has(eventName)); -} - -/** - * @param {Documentation.Class} classDesc - */ -function createEventDescriptions(classDesc) { - if (!hasUniqueEvents(classDesc)) - return []; - const descriptions = []; - for (let [eventName, value] of classDesc.events) { - eventName = eventName.toLowerCase(); - const type = stringifyComplexType(value && value.type, '', classDesc.name, eventName, 'payload'); - const argName = argNameForType(type); - const params = argName ? `${argName}: ${type}` : ''; - descriptions.push({ - type, - params, - eventName, - comment: value.comment - }); - } - return descriptions; -} - -/** - * @param {Documentation.Class} classDesc - */ -function classBody(classDesc) { - const parts = []; - const eventDescriptions = createEventDescriptions(classDesc); - const commentForMethod = { - off: 'Removes an event listener added by `on` or `addListener`.', - removeListener: 'Removes an event listener added by `on` or `addListener`.', - once: 'Adds an event listener that will be automatically removed after it is triggered once. See `addListener` for more information about this event.' - } - for (const method of ['on', 'once', 'addListener', 'removeListener', 'off']) { - for (const {eventName, params, comment} of eventDescriptions) { - if ((method === 'on' || method === 'addListener') && comment) - parts.push(writeComment(comment, ' ')); - else - parts.push(writeComment(commentForMethod[method], ' ')); - parts.push(` ${method}(event: '${eventName}', listener: (${params}) => void): this;\n`); - } - } - - const members = classDesc.membersArray.filter(member => member.kind !== 'event'); - parts.push(members.map(member => { - if (member.kind === 'event') - return ''; - if (member.alias === 'waitForEvent') { - const parts = []; - for (const {eventName, params, comment, type} of eventDescriptions) { - if (comment) - parts.push(writeComment(comment, ' ')); - parts.push(` ${member.alias}(event: '${eventName}', optionsOrPredicate?: { predicate?: (${params}) => boolean | Promise, timeout?: number } | ((${params}) => boolean | Promise)): Promise<${type}>;\n`); - } - - return parts.join('\n'); - } - const jsdoc = memberJSDOC(member, ' '); - const args = argsFromMember(member, ' ', classDesc.name); - let type = stringifyComplexType(member.type, ' ', classDesc.name, member.alias); - if (member.async) - type = `Promise<${type}>`; - // do this late, because we still want object definitions for overridden types - if (!hasOwnMethod(classDesc, member.alias)) - return ''; - return `${jsdoc}${member.alias}${args}: ${type};` - }).filter(x => x).join('\n\n')); - return parts.join('\n'); -} - -/** - * @param {Documentation.Class} classDesc - * @param {string} methodName - */ -function hasOwnMethod(classDesc, methodName) { - if (handledMethods.has(`${classDesc.name}.${methodName}`)) - return false; - while (classDesc = parentClass(classDesc)) { - if (classDesc.members.has(methodName)) - return false; - } - return true; -} - -/** - * @param {Documentation.Class} classDesc - */ -function parentClass(classDesc) { - if (!classDesc.extends) - return null; - return documentation.classes.get(classDesc.extends); -} - -function writeComment(comment, indent = '') { - const parts = []; - const out = []; - const pushLine = (line) => { - if (line || out[out.length - 1]) - out.push(line) - }; - let skipExample = false; - for (let line of comment.split('\n')) { - const match = line.match(/```(\w+)/); - if (match) { - const lang = match[1]; - skipExample = !["html", "yml", "bash", "js"].includes(lang); - } else if (skipExample && line.trim().startsWith('```')) { - skipExample = false; - continue; - } - if (!skipExample) - pushLine(line); - } - comment = out.join('\n'); - comment = comment.replace(/\[([^\]]+)\]\(\.\/([^\)]+)\)/g, (match, p1, p2) => { - return `[${p1}](https://playwright.dev/docs/${p2.replace('.md', '')})`; - }); - - parts.push(indent + '/**'); - parts.push(...comment.split('\n').map(line => indent + ' * ' + line.replace(/\*\//g, '*\\/'))); - parts.push(indent + ' */'); - return parts.join('\n'); -} - -/** - * @param {Documentation.Type} type - */ -function stringifyComplexType(type, indent, ...namespace) { - if (!type) - return 'void'; - return stringifySimpleType(type, indent, ...namespace); -} - -function stringifyObjectType(properties, name, indent = '') { - const parts = []; - parts.push(`{`); - parts.push(properties.map(member => `${memberJSDOC(member, indent + ' ')}${nameForProperty(member)}${argsFromMember(member, indent + ' ', name)}: ${stringifyComplexType(member.type, indent + ' ', name, member.name)};`).join('\n\n')); - parts.push(indent + '}'); - return parts.join('\n'); -} - -/** - * @param {Documentation.Type=} type - * @returns{string} - */ -function stringifySimpleType(type, indent = '', ...namespace) { - if (!type) - return 'void'; - if (type.name === 'Object' && type.templates) { - const keyType = stringifySimpleType(type.templates[0], indent, ...namespace); - const valueType = stringifySimpleType(type.templates[1], indent, ...namespace); - return `{ [key: ${keyType}]: ${valueType}; }`; - } - let out = type.name; - if (out === 'int' || out === 'float') - out = 'number'; - - if (type.name === 'Object' && type.properties && type.properties.length) { - const name = namespace.map(n => n[0].toUpperCase() + n.substring(1)).join(''); - const shouldExport = exported[name]; - const properties = namespace[namespace.length -1] === 'options' ? type.sortedProperties() : type.properties; - objectDefinitions.push({name, properties: properties}); - if (shouldExport) { - out = name; - } else { - out = stringifyObjectType(properties, name, indent); - } - } - - if (type.args) { - const stringArgs = type.args.map(a => ({ - type: stringifySimpleType(a, indent, ...namespace), - name: a.name.toLowerCase() - })); - out = `((${stringArgs.map(({name, type}) => `${name}: ${type}`).join(', ')}) => ${stringifySimpleType(type.returnType, indent, ...namespace)})`; - } else if (type.name === 'function') { - out = 'Function'; - } - if (out === 'path') - return 'string'; - if (out === 'Any') - return 'any'; - if (type.templates) - out += '<' + type.templates.map(t => stringifySimpleType(t, indent, ...namespace)).join(', ') + '>'; - if (type.union) - out = type.union.map(t => stringifySimpleType(t, indent, ...namespace)).join('|'); - return out.trim(); -} - -/** - * @param {Documentation.Member} member - */ -function argsFromMember(member, indent, ...namespace) { - if (member.kind === 'property') - return ''; - return '(' + member.argsArray.map(arg => `${nameForProperty(arg)}: ${stringifyComplexType(arg.type, indent, ...namespace, member.name, arg.name)}`).join(', ') + ')'; -} -/** - * @param {Documentation.Member} member - * @param {string} indent - */ -function memberJSDOC(member, indent) { - const lines = []; - if (member.comment) - lines.push(...member.comment.split('\n')); - if (member.deprecated) - lines.push('@deprecated'); - lines.push(...member.argsArray.map(arg => `@param ${arg.alias.replace(/\./g, '')} ${arg.comment.replace('\n', ' ')}`)); - if (!lines.length) - return indent; - return writeComment(lines.join('\n'), indent) + '\n' + indent; -} - -function generateDevicesTypes() { - const namedDevices = - Object.keys(devices) - .map(name => ` ${JSON.stringify(name)}: DeviceDescriptor;`) - .join('\n'); - return `type Devices = { -${namedDevices} - [key: string]: DeviceDescriptor; -}`; -} - -/** - * @param {Documentation.Member[]} args - */ -function renderJSSignature(args) { - const tokens = []; - let hasOptional = false; - for (const arg of args) { - const name = arg.alias; - const optional = !arg.required; - if (tokens.length) { - if (optional && !hasOptional) - tokens.push(`[, ${name}`); - else - tokens.push(`, ${name}`); - } else { - if (optional && !hasOptional) - tokens.push(`[${name}`); - else - tokens.push(`${name}`); - } - hasOptional = hasOptional || optional; - } - if (hasOptional) - tokens.push(']'); - return tokens.join(''); -} diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts new file mode 100644 index 0000000000..763c60a62f --- /dev/null +++ b/utils/generate_types/overrides-test.d.ts @@ -0,0 +1,338 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Browser, BrowserContext, BrowserContextOptions, Page, LaunchOptions, ViewportSize, Geolocation, HTTPCredentials } from './types'; +import type { Expect } from './testExpect'; + +export type { Expect } from './testExpect'; + +export type ReporterDescription = + ['dot'] | + ['line'] | + ['list'] | + ['junit'] | ['junit', { outputFile?: string, stripANSIControlSequences?: boolean }] | + ['json'] | ['json', { outputFile?: string }] | + ['null'] | + [string] | [string, any]; + +export type Shard = { total: number, current: number } | null; +export type ReportSlowTests = { max: number, threshold: number } | null; +export type PreserveOutput = 'always' | 'never' | 'failures-only'; +export type UpdateSnapshots = 'all' | 'none' | 'missing'; + +type FixtureDefine = { test: TestType, fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs> }; + +type ExpectSettings = { + toMatchSnapshot?: { + // Pixel match threshold. + threshold?: number + } +}; + +interface TestProject { + expect?: ExpectSettings; + metadata?: any; + name?: string; + outputDir?: string; + repeatEach?: number; + retries?: number; + testDir?: string; + testIgnore?: string | RegExp | (string | RegExp)[]; + testMatch?: string | RegExp | (string | RegExp)[]; + timeout?: number; +} + +export interface Project extends TestProject { + define?: FixtureDefine | FixtureDefine[]; + use?: Fixtures<{}, {}, TestArgs, WorkerArgs>; +} + +export type FullProject = Required>; + +export type LaunchConfig = { + /** + * Shell command to start. For example `npm run start`. + */ + command: string, + /** + * The port that your http server is expected to appear on. If specified it does wait until it accepts connections. + */ + waitForPort?: number, + /** + * How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. + */ + waitForPortTimeout?: number, + /** + * If true it will verify that the given port via `waitForPort` is available and throw otherwise. + * This should commonly set to !!process.env.CI to allow the local dev server when running tests locally. + */ + strict?: boolean + /** + * Environment variables, process.env by default + */ + env?: Record, + /** + * Current working directory of the spawned process. Default is process.cwd(). + */ + cwd?: string, +}; + +type LiteralUnion = T | (U & { zz_IGNORE_ME?: never }); + +interface TestConfig { + forbidOnly?: boolean; + globalSetup?: string; + globalTeardown?: string; + globalTimeout?: number; + grep?: RegExp | RegExp[]; + grepInvert?: RegExp | RegExp[]; + maxFailures?: number; + preserveOutput?: PreserveOutput; + projects?: Project[]; + quiet?: boolean; + reporter?: LiteralUnion<'list'|'dot'|'line'|'json'|'junit'|'null', string> | ReporterDescription[]; + reportSlowTests?: ReportSlowTests; + shard?: Shard; + updateSnapshots?: UpdateSnapshots; + _launch?: LaunchConfig | LaunchConfig[]; + workers?: number; + + expect?: ExpectSettings; + metadata?: any; + name?: string; + outputDir?: string; + repeatEach?: number; + retries?: number; + testDir?: string; + testIgnore?: string | RegExp | (string | RegExp)[]; + testMatch?: string | RegExp | (string | RegExp)[]; + timeout?: number; +} + +export interface Config extends TestConfig { + projects?: Project[]; + define?: FixtureDefine | FixtureDefine[]; + use?: Fixtures<{}, {}, TestArgs, WorkerArgs>; +} + +export interface FullConfig { + forbidOnly: boolean; + globalSetup: string | null; + globalTeardown: string | null; + globalTimeout: number; + grep: RegExp | RegExp[]; + grepInvert: RegExp | RegExp[] | null; + maxFailures: number; + preserveOutput: PreserveOutput; + projects: FullProject[]; + reporter: ReporterDescription[]; + reportSlowTests: ReportSlowTests; + rootDir: string; + quiet: boolean; + shard: Shard; + updateSnapshots: UpdateSnapshots; + workers: number; + _launch: LaunchConfig[]; +} + +export type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped'; + +export interface TestError { + message?: string; + stack?: string; + value?: string; +} + +export interface WorkerInfo { + config: FullConfig; + project: FullProject; + workerIndex: number; +} + +export interface TestInfo { + config: FullConfig; + project: FullProject; + workerIndex: number; + + title: string; + file: string; + line: number; + column: number; + fn: Function; + + skip(): void; + skip(condition: boolean): void; + skip(condition: boolean, description: string): void; + + fixme(): void; + fixme(condition: boolean): void; + fixme(condition: boolean, description: string): void; + + fail(): void; + fail(condition: boolean): void; + fail(condition: boolean, description: string): void; + + slow(): void; + slow(condition: boolean): void; + slow(condition: boolean, description: string): void; + + setTimeout(timeout: number): void; + expectedStatus: TestStatus; + timeout: number; + annotations: { type: string, description?: string }[]; + attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; + repeatEachIndex: number; + retry: number; + duration: number; + status?: TestStatus; + error?: TestError; + stdout: (string | Buffer)[]; + stderr: (string | Buffer)[]; + snapshotSuffix: string; + outputDir: string; + snapshotPath: (snapshotName: string) => string; + outputPath: (...pathSegments: string[]) => string; +} + +interface SuiteFunction { + (title: string, callback: () => void): void; +} + +interface TestFunction { + (title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise | void): void; +} + +export interface TestType extends TestFunction { + only: TestFunction; + describe: SuiteFunction & { + only: SuiteFunction; + }; + skip(): void; + skip(condition: boolean): void; + skip(condition: boolean, description: string): void; + skip(callback: (args: TestArgs & WorkerArgs) => boolean): void; + skip(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; + fixme(): void; + fixme(condition: boolean): void; + fixme(condition: boolean, description: string): void; + fixme(callback: (args: TestArgs & WorkerArgs) => boolean): void; + fixme(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; + fail(): void; + fail(condition: boolean): void; + fail(condition: boolean, description: string): void; + fail(callback: (args: TestArgs & WorkerArgs) => boolean): void; + fail(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; + slow(): void; + slow(condition: boolean): void; + slow(condition: boolean, description: string): void; + slow(callback: (args: TestArgs & WorkerArgs) => boolean): void; + slow(callback: (args: TestArgs & WorkerArgs) => boolean, description: string): void; + setTimeout(timeout: number): void; + beforeEach(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise | any): void; + afterEach(inner: (args: TestArgs & WorkerArgs, testInfo: TestInfo) => Promise | any): void; + beforeAll(inner: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise | any): void; + afterAll(inner: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise | any): void; + use(fixtures: Fixtures<{}, {}, TestArgs, WorkerArgs>): void; + expect: Expect; + declare(): TestType; + extend(fixtures: Fixtures): TestType; +} + +type KeyValue = { [key: string]: any }; +export type TestFixture = (args: Args, use: (r: R) => Promise, testInfo: TestInfo) => any; +export type WorkerFixture = (args: Args, use: (r: R) => Promise, workerInfo: WorkerInfo) => any; +type TestFixtureValue = R | TestFixture; +type WorkerFixtureValue = R | WorkerFixture; +export type Fixtures = { + [K in keyof PW]?: WorkerFixtureValue; +} & { + [K in keyof PT]?: TestFixtureValue; +} & { + [K in keyof W]?: [WorkerFixtureValue, { scope: 'worker', auto?: boolean }]; +} & { + [K in keyof T]?: TestFixtureValue | [TestFixtureValue, { scope?: 'test', auto?: boolean }]; +}; + +type BrowserName = 'chromium' | 'firefox' | 'webkit'; +type BrowserChannel = Exclude; +type ColorScheme = Exclude; +type ExtraHTTPHeaders = Exclude; +type Proxy = Exclude; +type StorageState = Exclude; + +export interface PlaywrightWorkerOptions { + browserName: BrowserName; + defaultBrowserType: BrowserName; + headless: boolean | undefined; + channel: BrowserChannel | undefined; + launchOptions: LaunchOptions; +} + +export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-video'; + +export interface PlaywrightTestOptions { + screenshot: 'off' | 'on' | 'only-on-failure'; + trace: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | /** deprecated */ 'retry-with-trace'; + video: VideoMode | { mode: VideoMode, size: ViewportSize }; + acceptDownloads: boolean | undefined; + bypassCSP: boolean | undefined; + colorScheme: ColorScheme | undefined; + deviceScaleFactor: number | undefined; + extraHTTPHeaders: ExtraHTTPHeaders | undefined; + geolocation: Geolocation | undefined; + hasTouch: boolean | undefined; + httpCredentials: HTTPCredentials | undefined; + ignoreHTTPSErrors: boolean | undefined; + isMobile: boolean | undefined; + javaScriptEnabled: boolean | undefined; + locale: string | undefined; + offline: boolean | undefined; + permissions: string[] | undefined; + proxy: Proxy | undefined; + storageState: StorageState | undefined; + timezoneId: string | undefined; + userAgent: string | undefined; + viewport: ViewportSize | null | undefined; + baseURL: string | undefined; + contextOptions: BrowserContextOptions; +} + + +export interface PlaywrightWorkerArgs { + playwright: typeof import('..'); + browser: Browser; +} + +export interface PlaywrightTestArgs { + context: BrowserContext; + page: Page; +} + +export type PlaywrightTestProject = Project; +export type PlaywrightTestConfig = Config; + +/** + * These tests are executed in Playwright environment that launches the browser + * and provides a fresh page to each test. + */ +export const test: TestType; +export default test; + +export const _baseTest: TestType<{}, {}>; +export const expect: Expect; + +// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 +export {}; diff --git a/utils/generate_types/overrides-testReporter.d.ts b/utils/generate_types/overrides-testReporter.d.ts new file mode 100644 index 0000000000..372935d66d --- /dev/null +++ b/utils/generate_types/overrides-testReporter.d.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { FullConfig, TestStatus, TestError } from './test'; +export type { FullConfig, TestStatus, TestError } from './test'; + +export interface Location { + file: string; + line: number; + column: number; +} + +export interface Suite { + title: string; + location?: Location; + suites: Suite[]; + tests: TestCase[]; + titlePath(): string[]; + allTests(): TestCase[]; +} + +export interface TestCase { + title: string; + location: Location; + titlePath(): string[]; + expectedStatus: TestStatus; + timeout: number; + annotations: { type: string, description?: string }[]; + retries: number; + results: TestResult[]; + outcome(): 'skipped' | 'expected' | 'unexpected' | 'flaky'; + ok(): boolean; +} + +export interface TestResult { + retry: number; + workerIndex: number; + startTime: Date; + duration: number; + status?: TestStatus; + error?: TestError; + attachments: { name: string, path?: string, body?: Buffer, contentType: string }[]; + stdout: (string | Buffer)[]; + stderr: (string | Buffer)[]; +} + +/** + * Result of the full test run. + */ +export interface FullResult { + /** + * Status: + * - 'passed' - everything went as expected. + * - 'failed' - any test has failed. + * - 'timedout' - the global time has been reached. + * - 'interrupted' - interrupted by the user. + */ + status: 'passed' | 'failed' | 'timedout' | 'interrupted'; +} + +export interface Reporter { + onBegin?(config: FullConfig, suite: Suite): void; + onTestBegin?(test: TestCase): void; + onStdOut?(chunk: string | Buffer, test?: TestCase): void; + onStdErr?(chunk: string | Buffer, test?: TestCase): void; + onTestEnd?(test: TestCase, result: TestResult): void; + onError?(error: TestError): void; + onEnd?(result: FullResult): void | Promise; +} + +// This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 +export {}; diff --git a/utils/generate_types/parseOverrides.js b/utils/generate_types/parseOverrides.js index b4da73352e..a64fd14b18 100644 --- a/utils/generate_types/parseOverrides.js +++ b/utils/generate_types/parseOverrides.js @@ -16,13 +16,14 @@ const path = require('path'); const ts = require('typescript'); + /** - * @param {(className: string) => string} commentForClass - * @param {(className: string, methodName: string) => string} commentForMethod - * @param {(className: string) => string} extraForClass + * @param {string} filePath + * @param {(className: string) => string} commentForClass + * @param {(className: string, methodName: string) => string} commentForMethod + * @param {(className: string) => string} extraForClass */ -async function parseOverrides(commentForClass, commentForMethod, extraForClass) { - const filePath = path.join(__dirname, 'overrides.d.ts'); +async function parseOverrides(filePath, commentForClass, commentForMethod, extraForClass) { const program = ts.createProgram({ rootNames: [filePath], options: { @@ -75,7 +76,9 @@ async function parseOverrides(commentForClass, commentForMethod, extraForClass) for (const [name, member] of symbol.members || []) { if (member.flags & ts.SymbolFlags.TypeParameter) continue; - const pos = member.valueDeclaration.getStart(file, false) + if (!member.valueDeclaration) + continue; + const pos = member.valueDeclaration.getStart(file, false); replacers.push({ pos, text: commentForMethod(className, name),