mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: merge Selectors
into BrowserContext
in the protocol (#36017)
This commit is contained in:
parent
42ea95e1c1
commit
92d4ce30c6
@ -75,7 +75,12 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
}
|
||||
|
||||
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
|
||||
options = { ...this._browserType._playwright._defaultContextOptions, ...options };
|
||||
options = {
|
||||
...this._browserType._playwright._defaultContextOptions,
|
||||
...options,
|
||||
selectorEngines: this._browserType._playwright.selectors._selectorEngines,
|
||||
testIdAttributeName: this._browserType._playwright.selectors._testIdAttributeName,
|
||||
};
|
||||
const contextOptions = await prepareBrowserContextParams(this._platform, options);
|
||||
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
|
||||
const context = BrowserContext.from(response.context);
|
||||
|
@ -93,7 +93,13 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
async launchPersistentContext(userDataDir: string, options: LaunchPersistentContextOptions = {}): Promise<BrowserContext> {
|
||||
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
|
||||
assert(!(options as any).port, 'Cannot specify a port without launching as a server.');
|
||||
options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options };
|
||||
options = {
|
||||
...this._playwright._defaultLaunchOptions,
|
||||
...this._playwright._defaultContextOptions,
|
||||
...options,
|
||||
selectorEngines: this._playwright.selectors._selectorEngines,
|
||||
testIdAttributeName: this._playwright.selectors._testIdAttributeName,
|
||||
};
|
||||
const contextParams = await prepareBrowserContextParams(this._platform, options);
|
||||
const persistentParams: channels.BrowserTypeLaunchPersistentContextParams = {
|
||||
...contextParams,
|
||||
@ -157,7 +163,8 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
connection.close();
|
||||
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
|
||||
}
|
||||
playwright._setSelectors(this._playwright.selectors);
|
||||
this._playwright.selectors._playwrights.add(playwright);
|
||||
connection.on('close', () => this._playwright.selectors._playwrights.delete(playwright));
|
||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||
this._didLaunchBrowser(browser, {}, logger);
|
||||
browser._shouldCloseConnectionOnClose = true;
|
||||
|
@ -35,7 +35,6 @@ import { LocalUtils } from './localUtils';
|
||||
import { Request, Response, Route, WebSocket, WebSocketRoute } from './network';
|
||||
import { BindingCall, Page } from './page';
|
||||
import { Playwright } from './playwright';
|
||||
import { SelectorsOwner } from './selectors';
|
||||
import { Stream } from './stream';
|
||||
import { Tracing } from './tracing';
|
||||
import { Worker } from './worker';
|
||||
@ -311,9 +310,6 @@ export class Connection extends EventEmitter {
|
||||
case 'Stream':
|
||||
result = new Stream(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'Selectors':
|
||||
result = new SelectorsOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
case 'SocksSupport':
|
||||
result = new DummyChannelOwner(parent, type, guid, initializer);
|
||||
break;
|
||||
|
@ -21,7 +21,7 @@ import { ChannelOwner } from './channelOwner';
|
||||
import { Electron } from './electron';
|
||||
import { TimeoutError } from './errors';
|
||||
import { APIRequest } from './fetch';
|
||||
import { Selectors, SelectorsOwner } from './selectors';
|
||||
import { Selectors } from './selectors';
|
||||
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { BrowserContextOptions, LaunchOptions } from 'playwright-core';
|
||||
@ -62,23 +62,11 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||
this._bidiFirefox._playwright = this;
|
||||
this.devices = this._connection.localUtils()?.devices ?? {};
|
||||
this.selectors = new Selectors();
|
||||
this.selectors._playwrights.add(this);
|
||||
this.errors = { TimeoutError };
|
||||
|
||||
const selectorsOwner = SelectorsOwner.from(initializer.selectors);
|
||||
this.selectors._addChannel(selectorsOwner);
|
||||
this._connection.on('close', () => {
|
||||
this.selectors._removeChannel(selectorsOwner);
|
||||
});
|
||||
(global as any)._playwrightInstance = this;
|
||||
}
|
||||
|
||||
_setSelectors(selectors: Selectors) {
|
||||
const selectorsOwner = SelectorsOwner.from(this._initializer.selectors);
|
||||
this.selectors._removeChannel(selectorsOwner);
|
||||
this.selectors = selectors;
|
||||
this.selectors._addChannel(selectorsOwner);
|
||||
}
|
||||
|
||||
static from(channel: channels.PlaywrightChannel): Playwright {
|
||||
return (channel as any)._object;
|
||||
}
|
||||
|
@ -14,56 +14,36 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ChannelOwner } from './channelOwner';
|
||||
import { evaluationScript } from './clientHelper';
|
||||
import { setTestIdAttribute, testIdAttributeName } from './locator';
|
||||
import { emptyPlatform } from './platform';
|
||||
import { setTestIdAttribute } from './locator';
|
||||
|
||||
import type { SelectorEngine } from './types';
|
||||
import type * as api from '../../types/types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { Platform } from './platform';
|
||||
|
||||
let platform = emptyPlatform;
|
||||
|
||||
export function setPlatformForSelectors(p: Platform) {
|
||||
platform = p;
|
||||
}
|
||||
import type { Playwright } from './playwright';
|
||||
|
||||
export class Selectors implements api.Selectors {
|
||||
private _channels = new Set<SelectorsOwner>();
|
||||
private _registrations: channels.SelectorsRegisterParams[] = [];
|
||||
_playwrights = new Set<Playwright>();
|
||||
_selectorEngines: channels.SelectorEngine[] = [];
|
||||
_testIdAttributeName: string | undefined;
|
||||
|
||||
async register(name: string, script: string | (() => SelectorEngine) | { path?: string, content?: string }, options: { contentScript?: boolean } = {}): Promise<void> {
|
||||
const platform = this._playwrights.values().next().value!._platform;
|
||||
const source = await evaluationScript(platform, script, undefined, false);
|
||||
const params = { ...options, name, source };
|
||||
for (const channel of this._channels)
|
||||
await channel._channel.register(params);
|
||||
this._registrations.push(params);
|
||||
const selectorEngine: channels.SelectorEngine = { ...options, name, source };
|
||||
for (const playwright of this._playwrights) {
|
||||
for (const context of playwright._allContexts())
|
||||
await context._channel.registerSelectorEngine({ selectorEngine });
|
||||
}
|
||||
this._selectorEngines.push(selectorEngine);
|
||||
}
|
||||
|
||||
setTestIdAttribute(attributeName: string) {
|
||||
this._testIdAttributeName = attributeName;
|
||||
setTestIdAttribute(attributeName);
|
||||
for (const channel of this._channels)
|
||||
channel._channel.setTestIdAttributeName({ testIdAttributeName: attributeName }).catch(() => {});
|
||||
}
|
||||
|
||||
_addChannel(channel: SelectorsOwner) {
|
||||
this._channels.add(channel);
|
||||
for (const params of this._registrations) {
|
||||
// This should not fail except for connection closure, but just in case we catch.
|
||||
channel._channel.register(params).catch(() => {});
|
||||
channel._channel.setTestIdAttributeName({ testIdAttributeName: testIdAttributeName() }).catch(() => {});
|
||||
for (const playwright of this._playwrights) {
|
||||
for (const context of playwright._allContexts())
|
||||
context._channel.setTestIdAttributeName({ testIdAttributeName: attributeName }).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
_removeChannel(channel: SelectorsOwner) {
|
||||
this._channels.delete(channel);
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectorsOwner extends ChannelOwner<channels.SelectorsChannel> {
|
||||
static from(browser: channels.SelectorsChannel): SelectorsOwner {
|
||||
return (browser as any)._object;
|
||||
}
|
||||
}
|
||||
|
@ -19,14 +19,12 @@ import { BrowserServerLauncherImpl } from './browserServerImpl';
|
||||
import { DispatcherConnection, PlaywrightDispatcher, RootDispatcher, createPlaywright } from './server';
|
||||
import { nodePlatform } from './server/utils/nodePlatform';
|
||||
import { Connection } from './client/connection';
|
||||
import { setPlatformForSelectors } from './client/selectors';
|
||||
|
||||
import type { Playwright as PlaywrightAPI } from './client/playwright';
|
||||
import type { Language } from './utils';
|
||||
|
||||
export function createInProcessPlaywright(): PlaywrightAPI {
|
||||
const playwright = createPlaywright({ sdkLanguage: (process.env.PW_LANG_NAME as Language | undefined) || 'javascript' });
|
||||
setPlatformForSelectors(nodePlatform);
|
||||
const clientConnection = new Connection(nodePlatform);
|
||||
clientConnection.useRawBuffers();
|
||||
const dispatcherConnection = new DispatcherConnection(true /* local */);
|
||||
|
@ -21,13 +21,11 @@ import { Connection } from './client/connection';
|
||||
import { PipeTransport } from './server/utils/pipeTransport';
|
||||
import { ManualPromise } from './utils/isomorphic/manualPromise';
|
||||
import { nodePlatform } from './server/utils/nodePlatform';
|
||||
import { setPlatformForSelectors } from './client/selectors';
|
||||
|
||||
import type { Playwright } from './client/playwright';
|
||||
|
||||
|
||||
export async function start(env: any = {}): Promise<{ playwright: Playwright, stop: () => Promise<void> }> {
|
||||
setPlatformForSelectors(nodePlatform);
|
||||
const client = new PlaywrightClient(env);
|
||||
const playwright = await client._playwright;
|
||||
(playwright as any).driverProcess = client._driverProcess;
|
||||
|
@ -50,8 +50,6 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
|
||||
['SocksSupport.socksData', { internal: true, }],
|
||||
['SocksSupport.socksError', { internal: true, }],
|
||||
['SocksSupport.socksEnd', { internal: true, }],
|
||||
['Selectors.register', { internal: true, }],
|
||||
['Selectors.setTestIdAttributeName', { internal: true, }],
|
||||
['BrowserType.launch', { title: 'Launch browser', }],
|
||||
['BrowserType.launchPersistentContext', { title: 'Launch persistent context', }],
|
||||
['BrowserType.connectOverCDP', { title: 'Connect over CDP', }],
|
||||
@ -74,6 +72,8 @@ export const methodMetainfo = new Map<string, { internal?: boolean, title?: stri
|
||||
['BrowserContext.exposeBinding', { title: 'Expose binding', }],
|
||||
['BrowserContext.grantPermissions', { title: 'Grant permissions', }],
|
||||
['BrowserContext.newPage', { title: 'Create new page', }],
|
||||
['BrowserContext.registerSelectorEngine', { internal: true, }],
|
||||
['BrowserContext.setTestIdAttributeName', { internal: true, }],
|
||||
['BrowserContext.setExtraHTTPHeaders', { title: 'Set extra HTTP headers', }],
|
||||
['BrowserContext.setGeolocation', { title: 'Set geolocation', }],
|
||||
['BrowserContext.setHTTPCredentials', { title: 'Set HTTP credentials', }],
|
||||
|
@ -92,6 +92,11 @@ scheme.ExpectedTextValue = tObject({
|
||||
ignoreCase: tOptional(tBoolean),
|
||||
normalizeWhiteSpace: tOptional(tBoolean),
|
||||
});
|
||||
scheme.SelectorEngine = tObject({
|
||||
name: tString,
|
||||
source: tString,
|
||||
contentScript: tOptional(tBoolean),
|
||||
});
|
||||
scheme.AXNode = tObject({
|
||||
role: tString,
|
||||
name: tString,
|
||||
@ -372,7 +377,6 @@ scheme.PlaywrightInitializer = tObject({
|
||||
android: tChannel(['Android']),
|
||||
electron: tChannel(['Electron']),
|
||||
utils: tOptional(tChannel(['LocalUtils'])),
|
||||
selectors: tChannel(['Selectors']),
|
||||
preLaunchedBrowser: tOptional(tChannel(['Browser'])),
|
||||
preConnectedAndroidDevice: tOptional(tChannel(['AndroidDevice'])),
|
||||
socksSupport: tOptional(tChannel(['SocksSupport'])),
|
||||
@ -517,17 +521,6 @@ scheme.SocksSupportSocksEndParams = tObject({
|
||||
uid: tString,
|
||||
});
|
||||
scheme.SocksSupportSocksEndResult = tOptional(tObject({}));
|
||||
scheme.SelectorsInitializer = tOptional(tObject({}));
|
||||
scheme.SelectorsRegisterParams = tObject({
|
||||
name: tString,
|
||||
source: tString,
|
||||
contentScript: tOptional(tBoolean),
|
||||
});
|
||||
scheme.SelectorsRegisterResult = tOptional(tObject({}));
|
||||
scheme.SelectorsSetTestIdAttributeNameParams = tObject({
|
||||
testIdAttributeName: tString,
|
||||
});
|
||||
scheme.SelectorsSetTestIdAttributeNameResult = tOptional(tObject({}));
|
||||
scheme.BrowserTypeInitializer = tObject({
|
||||
executablePath: tString,
|
||||
name: tString,
|
||||
@ -642,6 +635,8 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
|
||||
recordHar: tOptional(tType('RecordHarOptions')),
|
||||
strictSelectors: tOptional(tBoolean),
|
||||
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
|
||||
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
userDataDir: tString,
|
||||
slowMo: tOptional(tNumber),
|
||||
});
|
||||
@ -729,6 +724,8 @@ scheme.BrowserNewContextParams = tObject({
|
||||
recordHar: tOptional(tType('RecordHarOptions')),
|
||||
strictSelectors: tOptional(tBoolean),
|
||||
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
|
||||
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
proxy: tOptional(tObject({
|
||||
server: tString,
|
||||
bypass: tOptional(tString),
|
||||
@ -799,6 +796,8 @@ scheme.BrowserNewContextForReuseParams = tObject({
|
||||
recordHar: tOptional(tType('RecordHarOptions')),
|
||||
strictSelectors: tOptional(tBoolean),
|
||||
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
|
||||
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
proxy: tOptional(tObject({
|
||||
server: tString,
|
||||
bypass: tOptional(tString),
|
||||
@ -963,6 +962,14 @@ scheme.BrowserContextNewPageParams = tOptional(tObject({}));
|
||||
scheme.BrowserContextNewPageResult = tObject({
|
||||
page: tChannel(['Page']),
|
||||
});
|
||||
scheme.BrowserContextRegisterSelectorEngineParams = tObject({
|
||||
selectorEngine: tType('SelectorEngine'),
|
||||
});
|
||||
scheme.BrowserContextRegisterSelectorEngineResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextSetTestIdAttributeNameParams = tObject({
|
||||
testIdAttributeName: tString,
|
||||
});
|
||||
scheme.BrowserContextSetTestIdAttributeNameResult = tOptional(tObject({}));
|
||||
scheme.BrowserContextSetExtraHTTPHeadersParams = tObject({
|
||||
headers: tArray(tType('NameValue')),
|
||||
});
|
||||
@ -2695,6 +2702,8 @@ scheme.AndroidDeviceLaunchBrowserParams = tObject({
|
||||
recordHar: tOptional(tType('RecordHarOptions')),
|
||||
strictSelectors: tOptional(tBoolean),
|
||||
serviceWorkers: tOptional(tEnum(['allow', 'block'])),
|
||||
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
pkg: tOptional(tString),
|
||||
args: tOptional(tArray(tString)),
|
||||
proxy: tOptional(tObject({
|
||||
|
@ -32,6 +32,7 @@ import { InitScript } from './page';
|
||||
import { Page, PageBinding } from './page';
|
||||
import { Recorder } from './recorder';
|
||||
import { RecorderApp } from './recorder/recorderApp';
|
||||
import { Selectors } from './selectors';
|
||||
import { Tracing } from './trace/recorder/tracing';
|
||||
import * as js from './javascript';
|
||||
import * as rawStorageSource from '../generated/storageScriptSource';
|
||||
@ -43,7 +44,6 @@ import type { Download } from './download';
|
||||
import type * as frames from './frames';
|
||||
import type { CallMetadata } from './instrumentation';
|
||||
import type { Progress, ProgressController } from './progress';
|
||||
import type { Selectors } from './selectors';
|
||||
import type { ClientCertificatesProxy } from './socksClientCertificatesInterceptor';
|
||||
import type { SerializedStorage } from '@injected/storageScript';
|
||||
import type * as types from './types';
|
||||
@ -81,7 +81,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
readonly _downloads = new Set<Download>();
|
||||
readonly _browser: Browser;
|
||||
readonly _browserContextId: string | undefined;
|
||||
private _selectors?: Selectors;
|
||||
private _selectors: Selectors;
|
||||
private _origins = new Set<string>();
|
||||
readonly _harRecorders = new Map<string, HarRecorder>();
|
||||
readonly tracing: Tracing;
|
||||
@ -106,6 +106,7 @@ export abstract class BrowserContext extends SdkObject {
|
||||
this._browserContextId = browserContextId;
|
||||
this._isPersistentContext = !browserContextId;
|
||||
this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
|
||||
this._selectors = new Selectors(options.selectorEngines || [], options.testIdAttributeName);
|
||||
|
||||
this.fetchRequest = new BrowserContextAPIRequestContext(this);
|
||||
|
||||
@ -120,12 +121,8 @@ export abstract class BrowserContext extends SdkObject {
|
||||
return this._isPersistentContext;
|
||||
}
|
||||
|
||||
setSelectors(selectors: Selectors) {
|
||||
this._selectors = selectors;
|
||||
}
|
||||
|
||||
selectors(): Selectors {
|
||||
return this._selectors || this.attribution.playwright.selectors;
|
||||
return this._selectors;
|
||||
}
|
||||
|
||||
async _initialize() {
|
||||
@ -183,6 +180,9 @@ export abstract class BrowserContext extends SdkObject {
|
||||
static reusableContextHash(params: channels.BrowserNewContextForReuseParams): string {
|
||||
const paramsCopy = { ...params };
|
||||
|
||||
if (paramsCopy.selectorEngines?.length === 0)
|
||||
delete paramsCopy.selectorEngines;
|
||||
|
||||
for (const k of Object.keys(paramsCopy)) {
|
||||
const key = k as keyof channels.BrowserNewContextForReuseParams;
|
||||
if (paramsCopy[key] === defaultNewContextParamValues[key])
|
||||
@ -200,6 +200,8 @@ export abstract class BrowserContext extends SdkObject {
|
||||
if (params) {
|
||||
for (const key of paramsThatAllowContextReuse)
|
||||
(this._options as any)[key] = params[key];
|
||||
if (params.testIdAttributeName)
|
||||
this.selectors().setTestIdAttributeName(params.testIdAttributeName);
|
||||
}
|
||||
|
||||
await this._cancelAllRoutesInFlight();
|
||||
@ -779,6 +781,7 @@ const paramsThatAllowContextReuse: (keyof channels.BrowserNewContextForReusePara
|
||||
'screen',
|
||||
'userAgent',
|
||||
'viewport',
|
||||
'testIdAttributeName',
|
||||
];
|
||||
|
||||
const defaultNewContextParamValues: channels.BrowserNewContextForReuseParams = {
|
||||
|
@ -358,6 +358,14 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, channel
|
||||
this._subscriptions.delete(params.event);
|
||||
}
|
||||
|
||||
async registerSelectorEngine(params: channels.BrowserContextRegisterSelectorEngineParams): Promise<void> {
|
||||
this._object.selectors().register(params.selectorEngine);
|
||||
}
|
||||
|
||||
async setTestIdAttributeName(params: channels.BrowserContextSetTestIdAttributeNameParams): Promise<void> {
|
||||
this._object.selectors().setTestIdAttributeName(params.testIdAttributeName);
|
||||
}
|
||||
|
||||
override _onDispose() {
|
||||
// Avoid protocol calls for the closed context.
|
||||
if (!this._context.isClosingOrClosed())
|
||||
|
@ -19,7 +19,6 @@ import { BrowserContextDispatcher } from './browserContextDispatcher';
|
||||
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
|
||||
import { Dispatcher } from './dispatcher';
|
||||
import { BrowserContext } from '../browserContext';
|
||||
import { Selectors } from '../selectors';
|
||||
import { ArtifactDispatcher } from './artifactDispatcher';
|
||||
|
||||
import type { BrowserTypeDispatcher } from './browserTypeDispatcher';
|
||||
@ -48,7 +47,7 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
}
|
||||
|
||||
async newContextForReuse(params: channels.BrowserNewContextForReuseParams, metadata: CallMetadata): Promise<channels.BrowserNewContextForReuseResult> {
|
||||
return await newContextForReuse(this._object, this, params, null, metadata);
|
||||
return await newContextForReuse(this._object, this, params, metadata);
|
||||
}
|
||||
|
||||
async stopPendingOperations(params: channels.BrowserStopPendingOperationsParams, metadata: CallMetadata): Promise<channels.BrowserStopPendingOperationsResult> {
|
||||
@ -95,13 +94,9 @@ export class BrowserDispatcher extends Dispatcher<Browser, channels.BrowserChann
|
||||
export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.BrowserChannel, RootDispatcher> implements channels.BrowserChannel {
|
||||
_type_Browser = true;
|
||||
private _contexts = new Set<BrowserContext>();
|
||||
readonly selectors: Selectors;
|
||||
|
||||
constructor(scope: RootDispatcher, browser: Browser) {
|
||||
super(scope, browser, 'Browser', { version: browser.version(), name: browser.options.name });
|
||||
// When we have a remotely-connected browser, each client gets a fresh Selector instance,
|
||||
// so that two clients do not interfere between each other.
|
||||
this.selectors = new Selectors();
|
||||
}
|
||||
|
||||
async newContext(params: channels.BrowserNewContextParams, metadata: CallMetadata): Promise<channels.BrowserNewContextResult> {
|
||||
@ -109,13 +104,12 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
|
||||
params.recordVideo.dir = this._object.options.artifactsDir;
|
||||
const context = await this._object.newContext(metadata, params);
|
||||
this._contexts.add(context);
|
||||
context.setSelectors(this.selectors);
|
||||
context.on(BrowserContext.Events.Close, () => this._contexts.delete(context));
|
||||
return { context: new BrowserContextDispatcher(this, context) };
|
||||
}
|
||||
|
||||
async newContextForReuse(params: channels.BrowserNewContextForReuseParams, metadata: CallMetadata): Promise<channels.BrowserNewContextForReuseResult> {
|
||||
return await newContextForReuse(this._object, this as any as BrowserDispatcher, params, this.selectors, metadata);
|
||||
return await newContextForReuse(this._object, this as any as BrowserDispatcher, params, metadata);
|
||||
}
|
||||
|
||||
async stopPendingOperations(params: channels.BrowserStopPendingOperationsParams, metadata: CallMetadata): Promise<channels.BrowserStopPendingOperationsResult> {
|
||||
@ -160,7 +154,7 @@ export class ConnectedBrowserDispatcher extends Dispatcher<Browser, channels.Bro
|
||||
}
|
||||
}
|
||||
|
||||
async function newContextForReuse(browser: Browser, scope: BrowserDispatcher, params: channels.BrowserNewContextForReuseParams, selectors: Selectors | null, metadata: CallMetadata): Promise<channels.BrowserNewContextForReuseResult> {
|
||||
async function newContextForReuse(browser: Browser, scope: BrowserDispatcher, params: channels.BrowserNewContextForReuseParams, metadata: CallMetadata): Promise<channels.BrowserNewContextForReuseResult> {
|
||||
const { context, needsReset } = await browser.newContextForReuse(params, metadata);
|
||||
if (needsReset) {
|
||||
const oldContextDispatcher = scope.connection.existingDispatcher<BrowserContextDispatcher>(context);
|
||||
@ -168,8 +162,6 @@ async function newContextForReuse(browser: Browser, scope: BrowserDispatcher, pa
|
||||
oldContextDispatcher._dispose();
|
||||
await context.resetForReuse(metadata, params);
|
||||
}
|
||||
if (selectors)
|
||||
context.setSelectors(selectors);
|
||||
const contextDispatcher = new BrowserContextDispatcher(scope, context);
|
||||
return { context: contextDispatcher };
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import { Dispatcher } from './dispatcher';
|
||||
import { ElectronDispatcher } from './electronDispatcher';
|
||||
import { LocalUtilsDispatcher } from './localUtilsDispatcher';
|
||||
import { APIRequestContextDispatcher } from './networkDispatchers';
|
||||
import { SelectorsDispatcher } from './selectorsDispatcher';
|
||||
import { createGuid } from '../utils/crypto';
|
||||
import { eventsHelper } from '../utils/eventsHelper';
|
||||
|
||||
@ -53,7 +52,6 @@ export class PlaywrightDispatcher extends Dispatcher<Playwright, channels.Playwr
|
||||
android,
|
||||
electron: new ElectronDispatcher(scope, playwright.electron),
|
||||
utils: playwright.options.isServer ? undefined : new LocalUtilsDispatcher(scope, playwright),
|
||||
selectors: new SelectorsDispatcher(scope, browserDispatcher?.selectors || playwright.selectors),
|
||||
preLaunchedBrowser: browserDispatcher,
|
||||
preConnectedAndroidDevice: prelaunchedAndroidDeviceDispatcher,
|
||||
socksSupport: socksProxy ? new SocksSupportDispatcher(scope, socksProxy) : undefined,
|
||||
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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 { Dispatcher } from './dispatcher';
|
||||
|
||||
import type { RootDispatcher } from './dispatcher';
|
||||
import type { Selectors } from '../selectors';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
export class SelectorsDispatcher extends Dispatcher<Selectors, channels.SelectorsChannel, RootDispatcher> implements channels.SelectorsChannel {
|
||||
_type_Selectors = true;
|
||||
|
||||
constructor(scope: RootDispatcher, selectors: Selectors) {
|
||||
super(scope, selectors, 'Selectors', {});
|
||||
}
|
||||
|
||||
async register(params: channels.SelectorsRegisterParams): Promise<void> {
|
||||
await this._object.register(params.name, params.source, params.contentScript);
|
||||
}
|
||||
|
||||
async setTestIdAttributeName(params: channels.SelectorsSetTestIdAttributeNameParams): Promise<void> {
|
||||
this._object.setTestIdAttributeName(params.testIdAttributeName);
|
||||
}
|
||||
}
|
@ -24,7 +24,6 @@ import { DebugController } from './debugController';
|
||||
import { Electron } from './electron/electron';
|
||||
import { Firefox } from './firefox/firefox';
|
||||
import { SdkObject, createInstrumentation } from './instrumentation';
|
||||
import { Selectors } from './selectors';
|
||||
import { WebKit } from './webkit/webkit';
|
||||
|
||||
import type { BrowserType } from './browserType';
|
||||
@ -41,7 +40,6 @@ type PlaywrightOptions = {
|
||||
};
|
||||
|
||||
export class Playwright extends SdkObject {
|
||||
readonly selectors: Selectors;
|
||||
readonly chromium: BrowserType;
|
||||
readonly android: Android;
|
||||
readonly electron: Electron;
|
||||
@ -74,7 +72,6 @@ export class Playwright extends SdkObject {
|
||||
this.webkit = new WebKit(this);
|
||||
this.electron = new Electron(this);
|
||||
this.android = new Android(this, new AdbBackend());
|
||||
this.selectors = new Selectors();
|
||||
this.debugController = new DebugController(this);
|
||||
}
|
||||
|
||||
|
@ -18,15 +18,16 @@ import { createGuid } from './utils/crypto';
|
||||
import { InvalidSelectorError, parseSelector, stringifySelector, visitAllSelectorParts } from '../utils/isomorphic/selectorParser';
|
||||
|
||||
import type { ParsedSelector } from '../utils/isomorphic/selectorParser';
|
||||
import type * as channels from '@protocol/channels';
|
||||
|
||||
export class Selectors {
|
||||
private readonly _builtinEngines: Set<string>;
|
||||
private readonly _builtinEnginesInMainWorld: Set<string>;
|
||||
readonly _engines: Map<string, { source: string, contentScript: boolean }>;
|
||||
readonly _engines: Map<string, channels.SelectorEngine>;
|
||||
readonly guid = `selectors@${createGuid()}`;
|
||||
private _testIdAttributeName: string = 'data-testid';
|
||||
private _testIdAttributeName: string;
|
||||
|
||||
constructor() {
|
||||
constructor(engines: channels.SelectorEngine[], testIdAttributeName: string | undefined) {
|
||||
// Note: keep in sync with InjectedScript class.
|
||||
this._builtinEngines = new Set([
|
||||
'css', 'css:light',
|
||||
@ -49,17 +50,20 @@ export class Selectors {
|
||||
'_react', '_vue',
|
||||
]);
|
||||
this._engines = new Map();
|
||||
this._testIdAttributeName = testIdAttributeName ?? 'data-testid';
|
||||
for (const engine of engines)
|
||||
this.register(engine);
|
||||
}
|
||||
|
||||
async register(name: string, source: string, contentScript: boolean = false): Promise<void> {
|
||||
if (!name.match(/^[a-zA-Z_0-9-]+$/))
|
||||
register(engine: channels.SelectorEngine) {
|
||||
if (!engine.name.match(/^[a-zA-Z_0-9-]+$/))
|
||||
throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters');
|
||||
// Note: we keep 'zs' for future use.
|
||||
if (this._builtinEngines.has(name) || name === 'zs' || name === 'zs:light')
|
||||
throw new Error(`"${name}" is a predefined selector engine`);
|
||||
if (this._engines.has(name))
|
||||
throw new Error(`"${name}" selector engine has been already registered`);
|
||||
this._engines.set(name, { source, contentScript });
|
||||
if (this._builtinEngines.has(engine.name) || engine.name === 'zs' || engine.name === 'zs:light')
|
||||
throw new Error(`"${engine.name}" is a predefined selector engine`);
|
||||
if (this._engines.has(engine.name))
|
||||
throw new Error(`"${engine.name}" selector engine has been already registered`);
|
||||
this._engines.set(engine.name, engine);
|
||||
}
|
||||
|
||||
testIdAttributeName(): string {
|
||||
@ -70,10 +74,6 @@ export class Selectors {
|
||||
this._testIdAttributeName = testIdAttributeName;
|
||||
}
|
||||
|
||||
unregisterAll() {
|
||||
this._engines.clear();
|
||||
}
|
||||
|
||||
parseSelector(selector: string | ParsedSelector, strict: boolean) {
|
||||
const parsed = typeof selector === 'string' ? parseSelector(selector) : selector;
|
||||
let needsMainWorld = false;
|
||||
|
71
packages/protocol/src/channels.d.ts
vendored
71
packages/protocol/src/channels.d.ts
vendored
@ -52,7 +52,6 @@ export type InitializerTraits<T> =
|
||||
T extends EventTargetChannel ? EventTargetInitializer :
|
||||
T extends BrowserChannel ? BrowserInitializer :
|
||||
T extends BrowserTypeChannel ? BrowserTypeInitializer :
|
||||
T extends SelectorsChannel ? SelectorsInitializer :
|
||||
T extends SocksSupportChannel ? SocksSupportInitializer :
|
||||
T extends DebugControllerChannel ? DebugControllerInitializer :
|
||||
T extends PlaywrightChannel ? PlaywrightInitializer :
|
||||
@ -90,7 +89,6 @@ export type EventsTraits<T> =
|
||||
T extends EventTargetChannel ? EventTargetEvents :
|
||||
T extends BrowserChannel ? BrowserEvents :
|
||||
T extends BrowserTypeChannel ? BrowserTypeEvents :
|
||||
T extends SelectorsChannel ? SelectorsEvents :
|
||||
T extends SocksSupportChannel ? SocksSupportEvents :
|
||||
T extends DebugControllerChannel ? DebugControllerEvents :
|
||||
T extends PlaywrightChannel ? PlaywrightEvents :
|
||||
@ -128,7 +126,6 @@ export type EventTargetTraits<T> =
|
||||
T extends EventTargetChannel ? EventTargetEventTarget :
|
||||
T extends BrowserChannel ? BrowserEventTarget :
|
||||
T extends BrowserTypeChannel ? BrowserTypeEventTarget :
|
||||
T extends SelectorsChannel ? SelectorsEventTarget :
|
||||
T extends SocksSupportChannel ? SocksSupportEventTarget :
|
||||
T extends DebugControllerChannel ? DebugControllerEventTarget :
|
||||
T extends PlaywrightChannel ? PlaywrightEventTarget :
|
||||
@ -217,6 +214,12 @@ export type ExpectedTextValue = {
|
||||
normalizeWhiteSpace?: boolean,
|
||||
};
|
||||
|
||||
export type SelectorEngine = {
|
||||
name: string,
|
||||
source: string,
|
||||
contentScript?: boolean,
|
||||
};
|
||||
|
||||
export type AXNode = {
|
||||
role: string,
|
||||
name: string,
|
||||
@ -622,7 +625,6 @@ export type PlaywrightInitializer = {
|
||||
android: AndroidChannel,
|
||||
electron: ElectronChannel,
|
||||
utils?: LocalUtilsChannel,
|
||||
selectors: SelectorsChannel,
|
||||
preLaunchedBrowser?: BrowserChannel,
|
||||
preConnectedAndroidDevice?: AndroidDeviceChannel,
|
||||
socksSupport?: SocksSupportChannel,
|
||||
@ -897,35 +899,6 @@ export interface SocksSupportEvents {
|
||||
'socksClosed': SocksSupportSocksClosedEvent;
|
||||
}
|
||||
|
||||
// ----------- Selectors -----------
|
||||
export type SelectorsInitializer = {};
|
||||
export interface SelectorsEventTarget {
|
||||
}
|
||||
export interface SelectorsChannel extends SelectorsEventTarget, Channel {
|
||||
_type_Selectors: boolean;
|
||||
register(params: SelectorsRegisterParams, metadata?: CallMetadata): Promise<SelectorsRegisterResult>;
|
||||
setTestIdAttributeName(params: SelectorsSetTestIdAttributeNameParams, metadata?: CallMetadata): Promise<SelectorsSetTestIdAttributeNameResult>;
|
||||
}
|
||||
export type SelectorsRegisterParams = {
|
||||
name: string,
|
||||
source: string,
|
||||
contentScript?: boolean,
|
||||
};
|
||||
export type SelectorsRegisterOptions = {
|
||||
contentScript?: boolean,
|
||||
};
|
||||
export type SelectorsRegisterResult = void;
|
||||
export type SelectorsSetTestIdAttributeNameParams = {
|
||||
testIdAttributeName: string,
|
||||
};
|
||||
export type SelectorsSetTestIdAttributeNameOptions = {
|
||||
|
||||
};
|
||||
export type SelectorsSetTestIdAttributeNameResult = void;
|
||||
|
||||
export interface SelectorsEvents {
|
||||
}
|
||||
|
||||
// ----------- BrowserType -----------
|
||||
export type BrowserTypeInitializer = {
|
||||
executablePath: string,
|
||||
@ -1075,6 +1048,8 @@ export type BrowserTypeLaunchPersistentContextParams = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
userDataDir: string,
|
||||
slowMo?: number,
|
||||
};
|
||||
@ -1157,6 +1132,8 @@ export type BrowserTypeLaunchPersistentContextOptions = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
slowMo?: number,
|
||||
};
|
||||
export type BrowserTypeLaunchPersistentContextResult = {
|
||||
@ -1272,6 +1249,8 @@ export type BrowserNewContextParams = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
proxy?: {
|
||||
server: string,
|
||||
bypass?: string,
|
||||
@ -1339,6 +1318,8 @@ export type BrowserNewContextOptions = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
proxy?: {
|
||||
server: string,
|
||||
bypass?: string,
|
||||
@ -1409,6 +1390,8 @@ export type BrowserNewContextForReuseParams = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
proxy?: {
|
||||
server: string,
|
||||
bypass?: string,
|
||||
@ -1476,6 +1459,8 @@ export type BrowserNewContextForReuseOptions = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
proxy?: {
|
||||
server: string,
|
||||
bypass?: string,
|
||||
@ -1582,6 +1567,8 @@ export interface BrowserContextChannel extends BrowserContextEventTarget, EventT
|
||||
exposeBinding(params: BrowserContextExposeBindingParams, metadata?: CallMetadata): Promise<BrowserContextExposeBindingResult>;
|
||||
grantPermissions(params: BrowserContextGrantPermissionsParams, metadata?: CallMetadata): Promise<BrowserContextGrantPermissionsResult>;
|
||||
newPage(params?: BrowserContextNewPageParams, metadata?: CallMetadata): Promise<BrowserContextNewPageResult>;
|
||||
registerSelectorEngine(params: BrowserContextRegisterSelectorEngineParams, metadata?: CallMetadata): Promise<BrowserContextRegisterSelectorEngineResult>;
|
||||
setTestIdAttributeName(params: BrowserContextSetTestIdAttributeNameParams, metadata?: CallMetadata): Promise<BrowserContextSetTestIdAttributeNameResult>;
|
||||
setExtraHTTPHeaders(params: BrowserContextSetExtraHTTPHeadersParams, metadata?: CallMetadata): Promise<BrowserContextSetExtraHTTPHeadersResult>;
|
||||
setGeolocation(params: BrowserContextSetGeolocationParams, metadata?: CallMetadata): Promise<BrowserContextSetGeolocationResult>;
|
||||
setHTTPCredentials(params: BrowserContextSetHTTPCredentialsParams, metadata?: CallMetadata): Promise<BrowserContextSetHTTPCredentialsResult>;
|
||||
@ -1741,6 +1728,20 @@ export type BrowserContextNewPageOptions = {};
|
||||
export type BrowserContextNewPageResult = {
|
||||
page: PageChannel,
|
||||
};
|
||||
export type BrowserContextRegisterSelectorEngineParams = {
|
||||
selectorEngine: SelectorEngine,
|
||||
};
|
||||
export type BrowserContextRegisterSelectorEngineOptions = {
|
||||
|
||||
};
|
||||
export type BrowserContextRegisterSelectorEngineResult = void;
|
||||
export type BrowserContextSetTestIdAttributeNameParams = {
|
||||
testIdAttributeName: string,
|
||||
};
|
||||
export type BrowserContextSetTestIdAttributeNameOptions = {
|
||||
|
||||
};
|
||||
export type BrowserContextSetTestIdAttributeNameResult = void;
|
||||
export type BrowserContextSetExtraHTTPHeadersParams = {
|
||||
headers: NameValue[],
|
||||
};
|
||||
@ -4757,6 +4758,8 @@ export type AndroidDeviceLaunchBrowserParams = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
pkg?: string,
|
||||
args?: string[],
|
||||
proxy?: {
|
||||
@ -4822,6 +4825,8 @@ export type AndroidDeviceLaunchBrowserOptions = {
|
||||
recordHar?: RecordHarOptions,
|
||||
strictSelectors?: boolean,
|
||||
serviceWorkers?: 'allow' | 'block',
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
pkg?: string,
|
||||
args?: string[],
|
||||
proxy?: {
|
||||
|
@ -154,6 +154,14 @@ ExpectedTextValue:
|
||||
normalizeWhiteSpace: boolean?
|
||||
|
||||
|
||||
SelectorEngine:
|
||||
type: object
|
||||
properties:
|
||||
name: string
|
||||
source: string
|
||||
contentScript: boolean?
|
||||
|
||||
|
||||
AXNode:
|
||||
type: object
|
||||
properties:
|
||||
@ -610,6 +618,11 @@ ContextOptions:
|
||||
literals:
|
||||
- allow
|
||||
- block
|
||||
selectorEngines:
|
||||
type: array?
|
||||
items: SelectorEngine
|
||||
testIdAttributeName: string?
|
||||
|
||||
|
||||
LocalUtils:
|
||||
type: interface
|
||||
@ -780,7 +793,6 @@ Playwright:
|
||||
android: Android
|
||||
electron: Electron
|
||||
utils: LocalUtils?
|
||||
selectors: Selectors
|
||||
# Only present when connecting remotely via BrowserType.connect() method.
|
||||
preLaunchedBrowser: Browser?
|
||||
# Only present when connecting remotely via Android.connect() method.
|
||||
@ -996,22 +1008,6 @@ SocksSupport:
|
||||
parameters:
|
||||
uid: string
|
||||
|
||||
Selectors:
|
||||
type: interface
|
||||
|
||||
commands:
|
||||
|
||||
register:
|
||||
internal: true
|
||||
parameters:
|
||||
name: string
|
||||
source: string
|
||||
contentScript: boolean?
|
||||
|
||||
setTestIdAttributeName:
|
||||
internal: true
|
||||
parameters:
|
||||
testIdAttributeName: string
|
||||
|
||||
BrowserType:
|
||||
type: interface
|
||||
@ -1265,6 +1261,16 @@ BrowserContext:
|
||||
returns:
|
||||
page: Page
|
||||
|
||||
registerSelectorEngine:
|
||||
internal: true
|
||||
parameters:
|
||||
selectorEngine: SelectorEngine
|
||||
|
||||
setTestIdAttributeName:
|
||||
internal: true
|
||||
parameters:
|
||||
testIdAttributeName: string
|
||||
|
||||
setExtraHTTPHeaders:
|
||||
title: Set extra HTTP headers
|
||||
parameters:
|
||||
|
@ -43,7 +43,7 @@ export const testModeTest = test.extend<TestModeTestFixtures, TestModeWorkerOpti
|
||||
'driver': new DriverTestMode(),
|
||||
}[mode];
|
||||
const playwright = await testMode.setup();
|
||||
playwright._setSelectors(playwrightLibrary.selectors);
|
||||
(playwrightLibrary.selectors as any)._playwrights.add(playwright);
|
||||
await run(playwright);
|
||||
await testMode.teardown();
|
||||
}, { scope: 'worker' }],
|
||||
|
@ -53,7 +53,6 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
};
|
||||
expectScopeState(browser, GOLDEN_PRECONDITION);
|
||||
@ -88,7 +87,6 @@ it('should scope context handles', async ({ browserType, server, expectScopeStat
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
});
|
||||
|
||||
@ -115,7 +113,6 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
};
|
||||
expectScopeState(browserType, GOLDEN_PRECONDITION);
|
||||
@ -137,7 +134,6 @@ it('should scope CDPSession handles', async ({ browserType, browserName, expectS
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
});
|
||||
|
||||
@ -160,7 +156,6 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) =>
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
};
|
||||
expectScopeState(browserType, GOLDEN_PRECONDITION);
|
||||
@ -189,7 +184,6 @@ it('should scope browser handles', async ({ browserType, expectScopeState }) =>
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
});
|
||||
|
||||
@ -234,7 +228,6 @@ it('should not generate dispatchers for subresources w/o listeners', async ({ pa
|
||||
{ _guid: 'electron', objects: [] },
|
||||
{ _guid: 'localUtils', objects: [] },
|
||||
{ _guid: 'Playwright', objects: [] },
|
||||
{ _guid: 'selectors', objects: [] },
|
||||
]
|
||||
});
|
||||
});
|
||||
@ -353,10 +346,6 @@ it('exposeFunction should not leak', async ({ page, expectScopeState, server })
|
||||
'_guid': 'Playwright',
|
||||
'objects': [],
|
||||
},
|
||||
{
|
||||
'_guid': 'selectors',
|
||||
'objects': [],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
@ -136,9 +136,8 @@ it.describe('selector generator', () => {
|
||||
expect(await generate(page, '[data-testid="a"]')).toBe('internal:testid=[data-testid=\"a\"s]');
|
||||
});
|
||||
|
||||
it('should use data-testid in strict errors', async ({ page, playwright }) => {
|
||||
playwright.selectors.setTestIdAttribute('data-custom-id');
|
||||
await page.setContent(`
|
||||
it('should use data-testid in strict errors', async ({ contextFactory, page, playwright }) => {
|
||||
const content = `
|
||||
<div>
|
||||
<div></div>
|
||||
<div>
|
||||
@ -151,13 +150,27 @@ it.describe('selector generator', () => {
|
||||
</div>
|
||||
<div class='foo bar:1' data-custom-id='Two'>
|
||||
</div>
|
||||
</div>`);
|
||||
const error = await page.locator('.foo').hover().catch(e => e);
|
||||
expect(error.message).toContain('strict mode violation');
|
||||
expect(error.message).toContain('<div class=\"foo bar:0');
|
||||
expect(error.message).toContain('<div class=\"foo bar:1');
|
||||
expect(error.message).toContain(`aka getByTestId('One')`);
|
||||
expect(error.message).toContain(`aka getByTestId('Two')`);
|
||||
</div>
|
||||
`;
|
||||
|
||||
const checkPage = async (page: Page) => {
|
||||
await page.setContent(content);
|
||||
const error = await page.locator('.foo').hover().catch(e => e);
|
||||
expect(error.message).toContain('strict mode violation');
|
||||
expect(error.message).toContain('<div class=\"foo bar:0');
|
||||
expect(error.message).toContain('<div class=\"foo bar:1');
|
||||
expect(error.message).toContain(`aka getByTestId('One')`);
|
||||
expect(error.message).toContain(`aka getByTestId('Two')`);
|
||||
};
|
||||
|
||||
playwright.selectors.setTestIdAttribute('data-custom-id');
|
||||
// Check page and context that were created before setting the attribute.
|
||||
await checkPage(page);
|
||||
|
||||
const context2 = await contextFactory();
|
||||
const page2 = await context2.newPage();
|
||||
// Check page and context that were created after setting the attribute.
|
||||
await checkPage(page2);
|
||||
});
|
||||
|
||||
it('should handle first non-unique data-testid', async ({ page }) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user