feat(strict): add context-level selector strictness (#8290)

This commit is contained in:
Pavel Feldman 2021-08-18 12:51:45 -07:00 committed by GitHub
parent 1426f66ccd
commit 6ef76e333e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 119 additions and 20 deletions

View file

@ -511,6 +511,13 @@ contexts override the proxy, global proxy will be never used and can be any stri
`launch({ proxy: { server: 'http://per-context' } })`. `launch({ proxy: { server: 'http://per-context' } })`.
::: :::
## context-option-strict
- `strictSelectors` <[boolean]>
It specified, enables strict selectors mode for this context. In the strict selectors mode all operations
on selectors that imply single target DOM element will throw when more than one element matches the selector.
See [Locator] to learn more about the strict mode.
## select-options-values ## select-options-values
* langs: java, js, csharp * langs: java, js, csharp
- `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>> - `values` <[null]|[string]|[ElementHandle]|[Array]<[string]>|[Object]|[Array]<[ElementHandle]>|[Array]<[Object]>>
@ -637,6 +644,7 @@ using the [`method: AndroidDevice.setDefaultTimeout`] method.
- %%-context-option-recordvideo-%% - %%-context-option-recordvideo-%%
- %%-context-option-recordvideo-dir-%% - %%-context-option-recordvideo-dir-%%
- %%-context-option-recordvideo-size-%% - %%-context-option-recordvideo-size-%%
- %%-context-option-strict-%%
## browser-option-args ## browser-option-args
- `args` <[Array]<[string]>> - `args` <[Array]<[string]>>

View file

@ -342,6 +342,7 @@ export type BrowserTypeLaunchPersistentContextParams = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
userDataDir: string, userDataDir: string,
slowMo?: number, slowMo?: number,
}; };
@ -413,6 +414,7 @@ export type BrowserTypeLaunchPersistentContextOptions = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
slowMo?: number, slowMo?: number,
}; };
export type BrowserTypeLaunchPersistentContextResult = { export type BrowserTypeLaunchPersistentContextResult = {
@ -504,6 +506,7 @@ export type BrowserNewContextParams = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
proxy?: { proxy?: {
server: string, server: string,
bypass?: string, bypass?: string,
@ -562,6 +565,7 @@ export type BrowserNewContextOptions = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
proxy?: { proxy?: {
server: string, server: string,
bypass?: string, bypass?: string,
@ -2768,6 +2772,7 @@ export type ElectronLaunchParams = {
height: number, height: number,
}, },
}, },
strictSelectors?: boolean,
timezoneId?: string, timezoneId?: string,
}; };
export type ElectronLaunchOptions = { export type ElectronLaunchOptions = {
@ -2803,6 +2808,7 @@ export type ElectronLaunchOptions = {
height: number, height: number,
}, },
}, },
strictSelectors?: boolean,
timezoneId?: string, timezoneId?: string,
}; };
export type ElectronLaunchResult = { export type ElectronLaunchResult = {
@ -3134,6 +3140,7 @@ export type AndroidDeviceLaunchBrowserParams = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
proxy?: { proxy?: {
server: string, server: string,
bypass?: string, bypass?: string,
@ -3179,6 +3186,7 @@ export type AndroidDeviceLaunchBrowserOptions = {
omitContent?: boolean, omitContent?: boolean,
path: string, path: string,
}, },
strictSelectors?: boolean,
proxy?: { proxy?: {
server: string, server: string,
bypass?: string, bypass?: string,

View file

@ -323,6 +323,7 @@ ContextOptions:
properties: properties:
omitContent: boolean? omitContent: boolean?
path: string path: string
strictSelectors: boolean?
Playwright: Playwright:
@ -2393,6 +2394,7 @@ Electron:
properties: properties:
width: number width: number
height: number height: number
strictSelectors: boolean?
timezoneId: string? timezoneId: string?
returns: returns:
@ -2659,6 +2661,7 @@ AndroidDevice:
properties: properties:
omitContent: boolean? omitContent: boolean?
path: string path: string
strictSelectors: boolean?
proxy: proxy:
type: object? type: object?
properties: properties:

View file

@ -251,6 +251,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
omitContent: tOptional(tBoolean), omitContent: tOptional(tBoolean),
path: tString, path: tString,
})), })),
strictSelectors: tOptional(tBoolean),
userDataDir: tString, userDataDir: tString,
slowMo: tOptional(tNumber), slowMo: tOptional(tNumber),
}); });
@ -311,6 +312,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
omitContent: tOptional(tBoolean), omitContent: tOptional(tBoolean),
path: tString, path: tString,
})), })),
strictSelectors: tOptional(tBoolean),
proxy: tOptional(tObject({ proxy: tOptional(tObject({
server: tString, server: tString,
bypass: tOptional(tString), bypass: tOptional(tString),
@ -1090,6 +1092,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
height: tNumber, height: tNumber,
})), })),
})), })),
strictSelectors: tOptional(tBoolean),
timezoneId: tOptional(tString), timezoneId: tOptional(tString),
}); });
scheme.ElectronApplicationBrowserWindowParams = tObject({ scheme.ElectronApplicationBrowserWindowParams = tObject({
@ -1232,6 +1235,7 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
omitContent: tOptional(tBoolean), omitContent: tOptional(tBoolean),
path: tString, path: tString,
})), })),
strictSelectors: tOptional(tBoolean),
proxy: tOptional(tObject({ proxy: tOptional(tObject({
server: tString, server: tString,
bypass: tOptional(tString), bypass: tOptional(tString),

View file

@ -672,7 +672,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async querySelector(selector: string, options: types.StrictOptions): Promise<ElementHandle | null> { async querySelector(selector: string, options: types.StrictOptions): Promise<ElementHandle | null> {
return this._page.selectors._query(this._context.frame, selector, !!options.strict, this); return this._page.selectors.query(this._context.frame, selector, options, this);
} }
async querySelectorAll(selector: string): Promise<ElementHandle<Element>[]> { async querySelectorAll(selector: string): Promise<ElementHandle<Element>[]> {
@ -680,7 +680,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
} }
async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> { async evalOnSelectorAndWaitForSignals(selector: string, strict: boolean, expression: string, isFunction: boolean | undefined, arg: any): Promise<any> {
const handle = await this._page.selectors._query(this._context.frame, selector, strict, this); const handle = await this._page.selectors.query(this._context.frame, selector, { strict }, this);
if (!handle) if (!handle)
throw new Error(`Error: failed to find element matching selector "${selector}"`); throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg); const result = await handle.evaluateExpressionAndWaitForSignals(expression, isFunction, true, arg);
@ -743,7 +743,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
const { state = 'visible' } = options; const { state = 'visible' } = options;
if (!['attached', 'detached', 'visible', 'hidden'].includes(state)) if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
throw new Error(`state: expected one of (attached|detached|visible|hidden)`); throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = waitForSelectorTask(info, state, this); const task = waitForSelectorTask(info, state, this);
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
return controller.run(async progress => { return controller.run(async progress => {

View file

@ -684,7 +684,7 @@ export class Frame extends SdkObject {
async querySelector(selector: string, options: types.StrictOptions): Promise<dom.ElementHandle<Element> | null> { async querySelector(selector: string, options: types.StrictOptions): Promise<dom.ElementHandle<Element> | null> {
debugLogger.log('api', ` finding element using the selector "${selector}"`); debugLogger.log('api', ` finding element using the selector "${selector}"`);
return this._page.selectors._query(this, selector, !!options.strict); return this._page.selectors.query(this, selector, options);
} }
async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<dom.ElementHandle<Element> | null> { async waitForSelector(metadata: CallMetadata, selector: string, options: types.WaitForElementOptions = {}): Promise<dom.ElementHandle<Element> | null> {
@ -696,7 +696,7 @@ export class Frame extends SdkObject {
const { state = 'visible' } = options; const { state = 'visible' } = options;
if (!['attached', 'detached', 'visible', 'hidden'].includes(state)) if (!['attached', 'detached', 'visible', 'hidden'].includes(state))
throw new Error(`state: expected one of (attached|detached|visible|hidden)`); throw new Error(`state: expected one of (attached|detached|visible|hidden)`);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.waitForSelectorTask(info, state); const task = dom.waitForSelectorTask(info, state);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`); progress.log(`waiting for selector "${selector}"${state === 'attached' ? '' : ' to be ' + state}`);
@ -725,7 +725,7 @@ export class Frame extends SdkObject {
async dispatchEvent(metadata: CallMetadata, selector: string, type: string, eventInit?: Object, options: types.QueryOnSelectorOptions = {}): Promise<void> { async dispatchEvent(metadata: CallMetadata, selector: string, type: string, eventInit?: Object, options: types.QueryOnSelectorOptions = {}): Promise<void> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.dispatchEventTask(info, type, eventInit || {}); const task = dom.dispatchEventTask(info, type, eventInit || {});
await controller.run(async progress => { await controller.run(async progress => {
progress.log(`Dispatching "${type}" event on selector "${selector}"...`); progress.log(`Dispatching "${type}" event on selector "${selector}"...`);
@ -938,7 +938,7 @@ export class Frame extends SdkObject {
selector: string, selector: string,
strict: boolean, strict: boolean,
action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> { action: (handle: dom.ElementHandle<Element>) => Promise<R | 'error:notconnected'>): Promise<R> {
const info = this._page.selectors._parseSelector(selector, strict); const info = this._page.parseSelector(selector, { strict });
while (progress.isRunning()) { while (progress.isRunning()) {
progress.log(`waiting for selector "${selector}"`); progress.log(`waiting for selector "${selector}"`);
const task = dom.waitForSelectorTask(info, 'attached'); const task = dom.waitForSelectorTask(info, 'attached');
@ -1031,7 +1031,7 @@ export class Frame extends SdkObject {
async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string | null> { async textContent(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string | null> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.textContentTask(info); const task = dom.textContentTask(info);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(` retrieving textContent from "${selector}"`); progress.log(` retrieving textContent from "${selector}"`);
@ -1041,7 +1041,7 @@ export class Frame extends SdkObject {
async innerText(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string> { async innerText(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.innerTextTask(info); const task = dom.innerTextTask(info);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(` retrieving innerText from "${selector}"`); progress.log(` retrieving innerText from "${selector}"`);
@ -1052,7 +1052,7 @@ export class Frame extends SdkObject {
async innerHTML(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string> { async innerHTML(metadata: CallMetadata, selector: string, options: types.QueryOnSelectorOptions = {}): Promise<string> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.innerHTMLTask(info); const task = dom.innerHTMLTask(info);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(` retrieving innerHTML from "${selector}"`); progress.log(` retrieving innerHTML from "${selector}"`);
@ -1062,7 +1062,7 @@ export class Frame extends SdkObject {
async getAttribute(metadata: CallMetadata, selector: string, name: string, options: types.QueryOnSelectorOptions = {}): Promise<string | null> { async getAttribute(metadata: CallMetadata, selector: string, name: string, options: types.QueryOnSelectorOptions = {}): Promise<string | null> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.getAttributeTask(info, name); const task = dom.getAttributeTask(info, name);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(` retrieving attribute "${name}" from "${selector}"`); progress.log(` retrieving attribute "${name}" from "${selector}"`);
@ -1072,7 +1072,7 @@ export class Frame extends SdkObject {
async inputValue(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions = {}): Promise<string> { async inputValue(metadata: CallMetadata, selector: string, options: types.TimeoutOptions & types.StrictOptions = {}): Promise<string> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.inputValueTask(info); const task = dom.inputValueTask(info);
return controller.run(async progress => { return controller.run(async progress => {
progress.log(` retrieving value from "${selector}"`); progress.log(` retrieving value from "${selector}"`);
@ -1082,7 +1082,7 @@ export class Frame extends SdkObject {
private async _checkElementState(metadata: CallMetadata, selector: string, state: ElementStateWithoutStable, options: types.QueryOnSelectorOptions = {}): Promise<boolean> { private async _checkElementState(metadata: CallMetadata, selector: string, state: ElementStateWithoutStable, options: types.QueryOnSelectorOptions = {}): Promise<boolean> {
const controller = new ProgressController(metadata, this); const controller = new ProgressController(metadata, this);
const info = this._page.selectors._parseSelector(selector, !!options.strict); const info = this._page.parseSelector(selector, options);
const task = dom.elementStateTask(info, state); const task = dom.elementStateTask(info, state);
const result = await controller.run(async progress => { const result = await controller.run(async progress => {
progress.log(` checking "${state}" state of "${selector}"`); progress.log(` checking "${state}" state of "${selector}"`);

View file

@ -30,7 +30,7 @@ import { FileChooser } from './fileChooser';
import { Progress, ProgressController } from './progress'; import { Progress, ProgressController } from './progress';
import { assert, isError } from '../utils/utils'; import { assert, isError } from '../utils/utils';
import { debugLogger } from '../utils/debugLogger'; import { debugLogger } from '../utils/debugLogger';
import { Selectors } from './selectors'; import { SelectorInfo, Selectors } from './selectors';
import { CallMetadata, SdkObject } from './instrumentation'; import { CallMetadata, SdkObject } from './instrumentation';
import { Artifact } from './artifact'; import { Artifact } from './artifact';
@ -506,6 +506,11 @@ export class Page extends SdkObject {
firePageError(error: Error) { firePageError(error: Error) {
this.emit(Page.Events.PageError, error); this.emit(Page.Events.PageError, error);
} }
parseSelector(selector: string, options?: types.StrictOptions): SelectorInfo {
const strict = typeof options?.strict === 'boolean' ? options.strict : !!this.context()._options.strictSelectors;
return this.selectors.parseSelector(selector, strict);
}
} }
export class Worker extends SdkObject { export class Worker extends SdkObject {

View file

@ -68,13 +68,13 @@ export class Selectors {
this._engines.clear(); this._engines.clear();
} }
async _query(frame: frames.Frame, selector: string, strict: boolean, scope?: dom.ElementHandle): Promise<dom.ElementHandle<Element> | null> { async query(frame: frames.Frame, selector: string, options: { strict?: boolean }, scope?: dom.ElementHandle): Promise<dom.ElementHandle<Element> | null> {
const info = this._parseSelector(selector, strict); const info = frame._page.parseSelector(selector, options);
const context = await frame._context(info.world); const context = await frame._context(info.world);
const injectedScript = await context.injectedScript(); const injectedScript = await context.injectedScript();
const handle = await injectedScript.evaluateHandle((injected, { parsed, scope, strict }) => { const handle = await injectedScript.evaluateHandle((injected, { parsed, scope, strict }) => {
return injected.querySelector(parsed, scope || document, strict); return injected.querySelector(parsed, scope || document, strict);
}, { parsed: info.parsed, scope, strict }); }, { parsed: info.parsed, scope, strict: info.strict });
const elementHandle = handle.asElement() as dom.ElementHandle<Element> | null; const elementHandle = handle.asElement() as dom.ElementHandle<Element> | null;
if (!elementHandle) { if (!elementHandle) {
handle.dispose(); handle.dispose();
@ -85,7 +85,7 @@ export class Selectors {
} }
async _queryArray(frame: frames.Frame, selector: string, scope?: dom.ElementHandle): Promise<js.JSHandle<Element[]>> { async _queryArray(frame: frames.Frame, selector: string, scope?: dom.ElementHandle): Promise<js.JSHandle<Element[]>> {
const info = this._parseSelector(selector, false); const info = this.parseSelector(selector, false);
const context = await frame._mainContext(); const context = await frame._mainContext();
const injectedScript = await context.injectedScript(); const injectedScript = await context.injectedScript();
const arrayHandle = await injectedScript.evaluateHandle((injected, { parsed, scope }) => { const arrayHandle = await injectedScript.evaluateHandle((injected, { parsed, scope }) => {
@ -95,7 +95,7 @@ export class Selectors {
} }
async _queryAll(frame: frames.Frame, selector: string, scope?: dom.ElementHandle, adoptToMain?: boolean): Promise<dom.ElementHandle<Element>[]> { async _queryAll(frame: frames.Frame, selector: string, scope?: dom.ElementHandle, adoptToMain?: boolean): Promise<dom.ElementHandle<Element>[]> {
const info = this._parseSelector(selector, false); const info = this.parseSelector(selector, false);
const context = await frame._context(info.world); const context = await frame._context(info.world);
const injectedScript = await context.injectedScript(); const injectedScript = await context.injectedScript();
const arrayHandle = await injectedScript.evaluateHandle((injected, { parsed, scope }) => { const arrayHandle = await injectedScript.evaluateHandle((injected, { parsed, scope }) => {
@ -127,7 +127,7 @@ export class Selectors {
return adopted; return adopted;
} }
_parseSelector(selector: string, strict: boolean): SelectorInfo { parseSelector(selector: string, strict: boolean): SelectorInfo {
const parsed = parseSelector(selector); const parsed = parseSelector(selector);
let needsMainWorld = false; let needsMainWorld = false;
for (const part of parsed.parts) { for (const part of parsed.parts) {

View file

@ -274,6 +274,7 @@ export type BrowserContextOptions = {
omitContent?: boolean, omitContent?: boolean,
path: string path: string
}, },
strictSelectors?: boolean,
proxy?: ProxySettings, proxy?: ProxySettings,
baseURL?: string, baseURL?: string,
_debugName?: string, _debugName?: string,

View file

@ -0,0 +1,42 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* 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 { browserTest as it, expect } from './config/browserTest';
it('should not fail page.textContent in non-strict mode', async ({ page }) => {
await page.setContent(`<span>span1</span><div><span>target</span></div>`);
expect(await page.textContent('span', { strict: false })).toBe('span1');
});
it.describe('strict context mode', () => {
it.use({
contextOptions: async ({ contextOptions }, use) => {
const options = { ...contextOptions, strictSelectors: true };
await use(options);
}
});
it('should fail page.textContent in strict mode', async ({ page }) => {
await page.setContent(`<span>span1</span><div><span>target</span></div>`);
const error = await page.textContent('span').catch(e => e);
expect(error.message).toContain('strict mode violation');
});
it('should opt out of strict mode', async ({ page }) => {
await page.setContent(`<span>span1</span><div><span>target</span></div>`);
expect(await page.textContent('span', { strict: false })).toBe('span1');
});
});

28
types/types.d.ts vendored
View file

@ -8510,6 +8510,13 @@ export interface BrowserType<Unused = {}> {
*/ */
slowMo?: number; slowMo?: number;
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See [Locator] to learn
* more about the strict mode.
*/
strictSelectors?: boolean;
/** /**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to * Maximum time in milliseconds to wait for the browser instance to start. Defaults to `30000` (30 seconds). Pass `0` to
* disable timeout. * disable timeout.
@ -9562,6 +9569,13 @@ export interface AndroidDevice {
height: number; height: number;
}; };
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See [Locator] to learn
* more about the strict mode.
*/
strictSelectors?: boolean;
/** /**
* Changes the timezone of the context. See * 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) * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
@ -10417,6 +10431,13 @@ export interface Browser extends EventEmitter {
}>; }>;
}; };
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See [Locator] to learn
* more about the strict mode.
*/
strictSelectors?: boolean;
/** /**
* Changes the timezone of the context. See * 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) * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)
@ -12535,6 +12556,13 @@ export interface BrowserContextOptions {
}>; }>;
}; };
/**
* It specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See [Locator] to learn
* more about the strict mode.
*/
strictSelectors?: boolean;
/** /**
* Changes the timezone of the context. See * 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) * [ICU's metaZones.txt](https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1)