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`].
|
||||
|
||||
### 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 = %%-csharp-java-python-assertions-timeout-%%
|
||||
@ -1201,9 +1201,9 @@ await Expect(locator).ToHaveValueAsync(new Regex("[0-9]"));
|
||||
```
|
||||
|
||||
### 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 = %%-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.
|
||||
let received: string | undefined;
|
||||
@ -1072,7 +1086,7 @@ export class InjectedScript {
|
||||
element = this.retarget(element, 'follow-label')!;
|
||||
if (element.nodeName !== 'INPUT' && element.nodeName !== 'TEXTAREA' && element.nodeName !== 'SELECT')
|
||||
throw this.createStacklessError('Not an input element');
|
||||
received = (element as any).value;
|
||||
received = (element as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement).value;
|
||||
}
|
||||
|
||||
if (received !== undefined && options.expectedText) {
|
||||
|
@ -234,13 +234,20 @@ export function toHaveText(
|
||||
export function toHaveValue(
|
||||
this: ReturnType<Expect['getState']>,
|
||||
locator: LocatorEx,
|
||||
expected: string | RegExp,
|
||||
expected: string | RegExp | (string | RegExp)[],
|
||||
options?: { timeout?: number },
|
||||
) {
|
||||
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);
|
||||
if (Array.isArray(expected)) {
|
||||
return toEqual.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);
|
||||
} 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(
|
||||
|
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]/);
|
||||
* ```
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
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`.
|
||||
*/
|
||||
|
@ -412,6 +412,187 @@ test('should support toHaveValue failing', async ({ runInlineTest }) => {
|
||||
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 }) => {
|
||||
const result = await runInlineTest({
|
||||
'a.test.ts': `
|
||||
|
Loading…
x
Reference in New Issue
Block a user