mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
feat: support multi-select/combo box with toHaveValue (#14555)
This commit is contained in:
parent
d5bfd786b9
commit
e0a87e52d7
@ -293,9 +293,9 @@ Whether to use `element.innerText` instead of `element.textContent` when retriev
|
|||||||
The opposite of [`method: LocatorAssertions.toHaveValue`].
|
The opposite of [`method: LocatorAssertions.toHaveValue`].
|
||||||
|
|
||||||
### param: LocatorAssertions.NotToHaveValue.value
|
### param: LocatorAssertions.NotToHaveValue.value
|
||||||
- `value` <[string]|[RegExp]>
|
- `value` <[string]|[RegExp]|[Array]<[string]|[RegExp]>>
|
||||||
|
|
||||||
Expected value.
|
Expected value. A list of expected values can be used if the Locator is a `select` element with the `multiple` attribute.
|
||||||
|
|
||||||
### option: LocatorAssertions.NotToHaveValue.timeout = %%-js-assertions-timeout-%%
|
### option: LocatorAssertions.NotToHaveValue.timeout = %%-js-assertions-timeout-%%
|
||||||
### option: LocatorAssertions.NotToHaveValue.timeout = %%-csharp-java-python-assertions-timeout-%%
|
### option: LocatorAssertions.NotToHaveValue.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||||
@ -1201,9 +1201,9 @@ await Expect(locator).ToHaveValueAsync(new Regex("[0-9]"));
|
|||||||
```
|
```
|
||||||
|
|
||||||
### param: LocatorAssertions.toHaveValue.value
|
### param: LocatorAssertions.toHaveValue.value
|
||||||
- `value` <[string]|[RegExp]>
|
- `value` <[string]|[RegExp]|[Array]<[string]|[RegExp]>>
|
||||||
|
|
||||||
Expected value.
|
Expected value. A list of expected values can be used if the Locator is a `select` element with the `multiple` attribute.
|
||||||
|
|
||||||
### option: LocatorAssertions.toHaveValue.timeout = %%-js-assertions-timeout-%%
|
### option: LocatorAssertions.toHaveValue.timeout = %%-js-assertions-timeout-%%
|
||||||
### option: LocatorAssertions.toHaveValue.timeout = %%-csharp-java-python-assertions-timeout-%%
|
### option: LocatorAssertions.toHaveValue.timeout = %%-csharp-java-python-assertions-timeout-%%
|
||||||
|
@ -1051,6 +1051,20 @@ export class InjectedScript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multi-Select/Combobox
|
||||||
|
{
|
||||||
|
if (expression === 'to.have.value' && options.expectedText?.length && options.expectedText.length >= 2) {
|
||||||
|
element = this.retarget(element, 'follow-label')!;
|
||||||
|
if (element.nodeName !== 'SELECT' || !(element as HTMLSelectElement).multiple)
|
||||||
|
throw this.createStacklessError('Not a select element with a multiple attribute');
|
||||||
|
|
||||||
|
const received = [...(element as HTMLSelectElement).selectedOptions].map(o => o.value);
|
||||||
|
if (received.length !== options.expectedText.length)
|
||||||
|
return { received, matches: false };
|
||||||
|
return { received, matches: received.map((r, i) => new ExpectedTextMatcher(options.expectedText![i]).matches(r)).every(Boolean) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// Single text value.
|
// Single text value.
|
||||||
let received: string | undefined;
|
let received: string | undefined;
|
||||||
@ -1072,7 +1086,7 @@ export class InjectedScript {
|
|||||||
element = this.retarget(element, 'follow-label')!;
|
element = this.retarget(element, 'follow-label')!;
|
||||||
if (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')
|
if (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')
|
||||||
throw this.createStacklessError('Not an input element');
|
throw this.createStacklessError('Not an input element');
|
||||||
received = (element as any).value;
|
received = (element as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (received !== undefined && options.expectedText) {
|
if (received !== undefined && options.expectedText) {
|
||||||
|
@ -234,13 +234,20 @@ export function toHaveText(
|
|||||||
export function toHaveValue(
|
export function toHaveValue(
|
||||||
this: ReturnType<Expect['getState']>,
|
this: ReturnType<Expect['getState']>,
|
||||||
locator: LocatorEx,
|
locator: LocatorEx,
|
||||||
expected: string | RegExp,
|
expected: string | RegExp | (string | RegExp)[],
|
||||||
options?: { timeout?: number },
|
options?: { timeout?: number },
|
||||||
) {
|
) {
|
||||||
return toMatchText.call(this, 'toHaveValue', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
if (Array.isArray(expected)) {
|
||||||
const expectedText = toExpectedTextValues([expected]);
|
return toEqual.call(this, 'toHaveValue', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||||
return await locator._expect(customStackTrace, 'to.have.value', { expectedText, isNot, timeout });
|
const expectedText = toExpectedTextValues(expected);
|
||||||
}, expected, options);
|
return await locator._expect(customStackTrace, 'to.have.value', { expectedText, isNot, timeout });
|
||||||
|
}, expected, options);
|
||||||
|
} else {
|
||||||
|
return toMatchText.call(this, 'toHaveValue', locator, 'Locator', async (isNot, timeout, customStackTrace) => {
|
||||||
|
const expectedText = toExpectedTextValues([expected]);
|
||||||
|
return await locator._expect(customStackTrace, 'to.have.value', { expectedText, isNot, timeout });
|
||||||
|
}, expected, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toHaveTitle(
|
export function toHaveTitle(
|
||||||
|
4
packages/playwright-test/types/test.d.ts
vendored
4
packages/playwright-test/types/test.d.ts
vendored
@ -3528,10 +3528,10 @@ interface LocatorAssertions {
|
|||||||
* await expect(locator).toHaveValue(/[0-9]/);
|
* await expect(locator).toHaveValue(/[0-9]/);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param value Expected value.
|
* @param value Expected value. A list of expected values can be used if the Locator is a `select` element with the `multiple` attribute.
|
||||||
* @param options
|
* @param options
|
||||||
*/
|
*/
|
||||||
toHaveValue(value: string|RegExp, options?: {
|
toHaveValue(value: string|RegExp|Array<string|RegExp>, options?: {
|
||||||
/**
|
/**
|
||||||
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
|
* Time to retry the assertion for. Defaults to `timeout` in `TestConfig.expect`.
|
||||||
*/
|
*/
|
||||||
|
@ -412,6 +412,187 @@ test('should support toHaveValue failing', async ({ runInlineTest }) => {
|
|||||||
expect(result.output).toContain('"Text content"');
|
expect(result.output).toContain('"Text content"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('should support toHaveValue with multi-select', () => {
|
||||||
|
test('works with text', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<select multiple>
|
||||||
|
<option value="R">Red</option>
|
||||||
|
<option value="G">Green</option>
|
||||||
|
<option value="B">Blue</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('select');
|
||||||
|
await locator.selectOption(['R', 'G']);
|
||||||
|
await expect(locator).toHaveValue(['R', 'G']);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('follows labels', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<label for="colors">Pick a Color</label>
|
||||||
|
<select id="colors" multiple>
|
||||||
|
<option value="R">Red</option>
|
||||||
|
<option value="G">Green</option>
|
||||||
|
<option value="B">Blue</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('text=Pick a Color');
|
||||||
|
await locator.selectOption(['R', 'G']);
|
||||||
|
await expect(locator).toHaveValue(['R', 'G']);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exact match with text', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<select multiple>
|
||||||
|
<option value="RR">Red</option>
|
||||||
|
<option value="GG">Green</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('select');
|
||||||
|
await locator.selectOption(['RR', 'GG']);
|
||||||
|
await expect(locator).toHaveValue(['R', 'G']);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain(`
|
||||||
|
- Expected - 2
|
||||||
|
+ Received + 2
|
||||||
|
|
||||||
|
Array [
|
||||||
|
- "R",
|
||||||
|
- "G",
|
||||||
|
+ "RR",
|
||||||
|
+ "GG",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('works with regex', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<select multiple>
|
||||||
|
<option value="R">Red</option>
|
||||||
|
<option value="G">Green</option>
|
||||||
|
<option value="B">Blue</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('select');
|
||||||
|
await locator.selectOption(['R', 'G']);
|
||||||
|
await expect(locator).toHaveValue([/R/, /G/]);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails when items not selected', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<select multiple>
|
||||||
|
<option value="R">Red</option>
|
||||||
|
<option value="G">Green</option>
|
||||||
|
<option value="B">Blue</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('select');
|
||||||
|
await locator.selectOption(['B']);
|
||||||
|
await expect(locator).toHaveValue([/R/, /G/]);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(stripAnsi(result.output)).toContain(`
|
||||||
|
- Expected - 2
|
||||||
|
+ Received + 1
|
||||||
|
|
||||||
|
Array [
|
||||||
|
- /R/,
|
||||||
|
- /G/,
|
||||||
|
+ "B",
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails when multiple not specified', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<select>
|
||||||
|
<option value="R">Red</option>
|
||||||
|
<option value="G">Green</option>
|
||||||
|
<option value="B">Blue</option>
|
||||||
|
</select>
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('select');
|
||||||
|
await locator.selectOption(['B']);
|
||||||
|
await expect(locator).toHaveValue([/R/, /G/]);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.output).toContain('Not a select element with a multiple attribute');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails when not a select element', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent(\`
|
||||||
|
<input value="foo" />
|
||||||
|
\`);
|
||||||
|
const locator = page.locator('input');
|
||||||
|
await expect(locator).toHaveValue([/R/, /G/]);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
}, { workers: 1 });
|
||||||
|
expect(result.passed).toBe(0);
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.output).toContain('Not a select element with a multiple attribute');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('should print expected/received before timeout', async ({ runInlineTest }) => {
|
test('should print expected/received before timeout', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'a.test.ts': `
|
'a.test.ts': `
|
||||||
|
Loading…
x
Reference in New Issue
Block a user