diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 0a6fea4859..24a6e70088 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -3716,7 +3716,7 @@ export interface Page { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(selector: string, values: null|string|ElementHandle|Array|{ + selectOption(selector: string, values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -3731,7 +3731,7 @@ export interface Page { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -3930,7 +3930,7 @@ export interface Page { * @param files * @param options */ - setInputFiles(selector: string, files: string|Array|{ + setInputFiles(selector: string, files: string|ReadonlyArray|{ /** * File name */ @@ -3945,7 +3945,7 @@ export interface Page { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -6828,7 +6828,7 @@ export interface Frame { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(selector: string, values: null|string|ElementHandle|Array|{ + selectOption(selector: string, values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -6843,7 +6843,7 @@ export interface Frame { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -7000,7 +7000,7 @@ export interface Frame { * @param files * @param options */ - setInputFiles(selector: string, files: string|Array|{ + setInputFiles(selector: string, files: string|ReadonlyArray|{ /** * File name */ @@ -7015,7 +7015,7 @@ export interface Frame { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -8171,7 +8171,7 @@ export interface BrowserContext { * * For the cookie to apply to all subdomains as well, prefix domain with a dot, like this: ".example.com". */ - addCookies(cookies: Array<{ + addCookies(cookies: ReadonlyArray<{ name: string; value: string; @@ -8262,7 +8262,7 @@ export interface BrowserContext { * URLs are returned. * @param urls Optional list of URLs. */ - cookies(urls?: string|Array): Promise>; + cookies(urls?: string|ReadonlyArray): Promise>; /** * The method adds a function called `name` on the `window` object of every frame in every page in the context. When @@ -8327,7 +8327,7 @@ export interface BrowserContext { * - `'payment-handler'` * @param options */ - grantPermissions(permissions: Array, options?: { + grantPermissions(permissions: ReadonlyArray, options?: { /** * The [origin] to grant permissions to, e.g. "https://example.com". */ @@ -10082,7 +10082,7 @@ export interface ElementHandle extends JSHandle { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(values: null|string|ElementHandle|Array|{ + selectOption(values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -10097,7 +10097,7 @@ export interface ElementHandle extends JSHandle { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -10224,7 +10224,7 @@ export interface ElementHandle extends JSHandle { * @param files * @param options */ - setInputFiles(files: string|Array|{ + setInputFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -10239,7 +10239,7 @@ export interface ElementHandle extends JSHandle { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -12243,7 +12243,7 @@ export interface Locator { * labels. Option is considered matching if all specified properties match. * @param options */ - selectOption(values: null|string|ElementHandle|Array|{ + selectOption(values: null|string|ElementHandle|ReadonlyArray|{ /** * Matches by `option.value`. Optional. */ @@ -12258,7 +12258,7 @@ export interface Locator { * Matches by the index. Optional. */ index?: number; - }|Array|Array<{ + }|ReadonlyArray|ReadonlyArray<{ /** * Matches by `option.value`. Optional. */ @@ -12422,7 +12422,7 @@ export interface Locator { * @param files * @param options */ - setInputFiles(files: string|Array|{ + setInputFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -12437,7 +12437,7 @@ export interface Locator { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -14931,7 +14931,7 @@ export interface AndroidInput { x: number; y: number; - }, segments: Array<{ + }, segments: ReadonlyArray<{ x: number; y: number; @@ -17332,7 +17332,7 @@ export interface FileChooser { * @param files * @param options */ - setFiles(files: string|Array|{ + setFiles(files: string|ReadonlyArray|{ /** * File name */ @@ -17347,7 +17347,7 @@ export interface FileChooser { * File content */ buffer: Buffer; - }|Array<{ + }|ReadonlyArray<{ /** * File name */ @@ -18010,7 +18010,7 @@ export interface Logger { * @param args message arguments * @param hints optional formatting hints */ - log(name: string, severity: "verbose"|"info"|"warning"|"error", message: string|Error, args: Array, hints: { + log(name: string, severity: "verbose"|"info"|"warning"|"error", message: string|Error, args: ReadonlyArray, hints: { /** * Optional preferred logger color. */ diff --git a/packages/playwright/types/test.d.ts b/packages/playwright/types/test.d.ts index 72d542f2b2..e0713271cc 100644 --- a/packages/playwright/types/test.d.ts +++ b/packages/playwright/types/test.d.ts @@ -2072,7 +2072,7 @@ export interface TestInfo { * (i.e. `test-results/a-test-title`), otherwise it will throw. * @param pathSegments Path segments to append at the end of the resulting path. */ - outputPath(...pathSegments: Array): string; + outputPath(...pathSegments: ReadonlyArray): string; /** * Changes the timeout for the currently running test. Zero means no timeout. Learn more about @@ -2134,7 +2134,7 @@ export interface TestInfo { * @param pathSegments The name of the snapshot or the path segments to define the snapshot file path. Snapshots with the same name in the * same test file are expected to be the same. */ - snapshotPath(...pathSegments: Array): string; + snapshotPath(...pathSegments: ReadonlyArray): string; /** * The list of annotations applicable to the current test. Includes annotations from the test, annotations from all @@ -5740,7 +5740,7 @@ interface LocatorAssertions { * @param expected Expected substring or RegExp or a list of those. * @param options */ - toContainText(expected: string|RegExp|Array, options?: { + toContainText(expected: string|RegExp|ReadonlyArray, options?: { /** * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular * expression flag if specified. @@ -5831,7 +5831,7 @@ interface LocatorAssertions { * @param expected Expected class or RegExp or a list of those. * @param options */ - toHaveClass(expected: string|RegExp|Array, options?: { + toHaveClass(expected: string|RegExp|ReadonlyArray, options?: { /** * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ @@ -5936,7 +5936,7 @@ interface LocatorAssertions { * @param name Snapshot name. * @param options */ - toHaveScreenshot(name: string|Array, options?: { + toHaveScreenshot(name: string|ReadonlyArray, options?: { /** * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different * treatment depending on their duration: @@ -6153,7 +6153,7 @@ interface LocatorAssertions { * @param expected Expected string or RegExp or a list of those. * @param options */ - toHaveText(expected: string|RegExp|Array, options?: { + toHaveText(expected: string|RegExp|ReadonlyArray, options?: { /** * Whether to perform case-insensitive match. `ignoreCase` option takes precedence over the corresponding regular * expression flag if specified. @@ -6217,7 +6217,7 @@ interface LocatorAssertions { * @param values Expected options currently selected. * @param options */ - toHaveValues(values: Array, options?: { + toHaveValues(values: ReadonlyArray, options?: { /** * Time to retry the assertion for in milliseconds. Defaults to `timeout` in `TestConfig.expect`. */ @@ -6266,7 +6266,7 @@ interface PageAssertions { * @param name Snapshot name. * @param options */ - toHaveScreenshot(name: string|Array, options?: { + toHaveScreenshot(name: string|ReadonlyArray, options?: { /** * When set to `"disabled"`, stops CSS animations, CSS transitions and Web Animations. Animations get different * treatment depending on their duration: @@ -6585,7 +6585,7 @@ interface SnapshotAssertions { * @param name Snapshot name. * @param options */ - toMatchSnapshot(name: string|Array, options?: { + toMatchSnapshot(name: string|ReadonlyArray, options?: { /** * An acceptable ratio of pixels that are different to the total amount of pixels, between `0` and `1`. Default is * configurable with `TestConfig.expect`. Unset by default. diff --git a/tests/playwright-test/expect.spec.ts b/tests/playwright-test/expect.spec.ts index f725711943..e56034c886 100644 --- a/tests/playwright-test/expect.spec.ts +++ b/tests/playwright-test/expect.spec.ts @@ -223,6 +223,20 @@ test('should compile generic matchers', async ({ runTSC }) => { expect(result.exitCode).toBe(0); }); +test('should work when passing a ReadonlyArray', async ({ runTSC }) => { + const result = await runTSC({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('example', async ({ page }) => { + const readonlyArray: ReadonlyArray = ['1', '2', '3']; + expect(page.locator('.foo')).toHaveText(readonlyArray); + await page.locator('.foo').setInputFiles(readonlyArray); + }); + ` + }); + expect(result.exitCode).toBe(0); +}); + test('should work with expect message', async ({ runTSC }) => { const result = await runTSC({ 'a.spec.ts': ` diff --git a/utils/generate_types/index.js b/utils/generate_types/index.js index 70112e5a69..263ef1fb8d 100644 --- a/utils/generate_types/index.js +++ b/utils/generate_types/index.js @@ -246,7 +246,7 @@ class TypesGenerator { const descriptions = []; for (let [eventName, value] of classDesc.events) { eventName = eventName.toLowerCase(); - const type = this.stringifyComplexType(value && value.type, ' ', classDesc.name, eventName, 'payload'); + const type = this.stringifyComplexType(value && value.type, 'out', ' ', classDesc.name, eventName, 'payload'); const argName = this.argNameForType(type); const params = argName ? `${argName}: ${type}` : ''; descriptions.push({ @@ -300,7 +300,7 @@ class TypesGenerator { } const jsdoc = this.memberJSDOC(member, indent); const args = this.argsFromMember(member, indent, classDesc.name); - let type = this.stringifyComplexType(member.type, indent, classDesc.name, member.alias); + let type = this.stringifyComplexType(member.type, 'out', indent, classDesc.name, member.alias); if (member.async) type = `Promise<${type}>`; // do this late, because we still want object definitions for overridden types @@ -373,12 +373,12 @@ class TypesGenerator { } /** - * @param {docs.Type} type + * @param {docs.Type|null} type */ - stringifyComplexType(type, indent, ...namespace) { + stringifyComplexType(type, direction, indent, ...namespace) { if (!type) return 'void'; - return this.stringifySimpleType(type, indent, ...namespace); + return this.stringifySimpleType(type, direction, indent, ...namespace); } /** @@ -393,7 +393,7 @@ class TypesGenerator { parts.push(properties.map(member => { const comment = this.memberJSDOC(member, indent + ' '); const args = this.argsFromMember(member, indent + ' ', name); - const type = this.stringifyComplexType(member.type, indent + ' ', name, member.name); + const type = this.stringifyComplexType(member.type, 'out', indent + ' ', name, member.name); return `${comment}${this.nameForProperty(member)}${args}: ${type};`; }).join('\n\n')); parts.push(indent + '}'); @@ -401,21 +401,23 @@ class TypesGenerator { } /** - * @param {docs.Type=} type + * @param {docs.Type | null | undefined} type + * @param {'in' | 'out'} direction * @returns{string} */ - stringifySimpleType(type, indent = '', ...namespace) { + stringifySimpleType(type, direction, 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); + const keyType = this.stringifySimpleType(type.templates[0], direction, indent, ...namespace); + const valueType = this.stringifySimpleType(type.templates[1], direction, indent, ...namespace); return `{ [key: ${keyType}]: ${valueType}; }`; } let out = type.name; if (out === 'int' || out === 'float') out = 'number'; - + if (out === 'Array' && direction === 'in') + out = 'ReadonlyArray'; 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]; @@ -431,10 +433,10 @@ class TypesGenerator { if (type.args) { const stringArgs = type.args.map(a => ({ - type: this.stringifySimpleType(a, indent, ...namespace), + type: this.stringifySimpleType(a, direction, indent, ...namespace), name: a.name.toLowerCase() })); - out = `((${stringArgs.map(({ name, type }) => `${name}: ${type}`).join(', ')}) => ${this.stringifySimpleType(type.returnType, indent, ...namespace)})`; + out = `((${stringArgs.map(({ name, type }) => `${name}: ${type}`).join(', ')}) => ${this.stringifySimpleType(type.returnType, 'out', indent, ...namespace)})`; } else if (type.name === 'function') { out = 'Function'; } @@ -443,9 +445,9 @@ class TypesGenerator { if (out === 'Any') return 'any'; if (type.templates) - out += '<' + type.templates.map(t => this.stringifySimpleType(t, indent, ...namespace)).join(', ') + '>'; + out += '<' + type.templates.map(t => this.stringifySimpleType(t, direction, indent, ...namespace)).join(', ') + '>'; if (type.union) - out = type.union.map(t => this.stringifySimpleType(t, indent, ...namespace)).join('|'); + out = type.union.map(t => this.stringifySimpleType(t, direction, indent, ...namespace)).join('|'); return out.trim(); } @@ -455,7 +457,7 @@ class TypesGenerator { argsFromMember(member, indent, ...namespace) { if (member.kind === 'property') return ''; - return '(' + member.argsArray.map(arg => `${this.nameForProperty(arg)}: ${this.stringifyComplexType(arg.type, indent, ...namespace, member.alias, arg.alias)}`).join(', ') + ')'; + return '(' + member.argsArray.map(arg => `${this.nameForProperty(arg)}: ${this.stringifyComplexType(arg.type, 'in', indent, ...namespace, member.alias, arg.alias)}`).join(', ') + ')'; } /**