feat: support multi-select/combo box with toHaveValue (#14555)

This commit is contained in:
Ross Wollman 2022-06-02 12:10:28 -04:00 committed by GitHub
parent d5bfd786b9
commit e0a87e52d7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 214 additions and 12 deletions

View File

@ -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-%%

View File

@ -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) {

View File

@ -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(

View File

@ -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`.
*/

View File

@ -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': `