mirror of
https://github.com/microsoft/playwright.git
synced 2025-06-26 21:40:17 +00:00
chore: fix selector engines in electron and android (#36119)
This commit is contained in:
parent
8f773a4c06
commit
111f21ebac
@ -32,11 +32,13 @@ import type * as api from '../../types/types';
|
||||
import type { AndroidServerLauncherImpl } from '../androidServerImpl';
|
||||
import type { Platform } from './platform';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type { Playwright } from './playwright';
|
||||
|
||||
type Direction = 'down' | 'up' | 'left' | 'right';
|
||||
type SpeedOptions = { speed?: number };
|
||||
|
||||
export class Android extends ChannelOwner<channels.AndroidChannel> implements api.Android {
|
||||
_playwright!: Playwright;
|
||||
readonly _timeoutSettings: TimeoutSettings;
|
||||
_serverLauncher?: AndroidServerLauncherImpl;
|
||||
|
||||
@ -100,6 +102,7 @@ export class Android extends ChannelOwner<channels.AndroidChannel> implements ap
|
||||
export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> implements api.AndroidDevice {
|
||||
readonly _timeoutSettings: TimeoutSettings;
|
||||
private _webViews = new Map<string, AndroidWebView>();
|
||||
private _android: Android;
|
||||
_shouldCloseConnectionOnClose = false;
|
||||
|
||||
static from(androidDevice: channels.AndroidDeviceChannel): AndroidDevice {
|
||||
@ -110,6 +113,7 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||
|
||||
constructor(parent: ChannelOwner, type: string, guid: string, initializer: channels.AndroidDeviceInitializer) {
|
||||
super(parent, type, guid, initializer);
|
||||
this._android = parent as Android;
|
||||
this.input = new AndroidInput(this);
|
||||
this._timeoutSettings = new TimeoutSettings(this._platform, (parent as Android)._timeoutSettings);
|
||||
this._channel.on('webViewAdded', ({ webView }) => this._onWebViewAdded(webView));
|
||||
@ -257,7 +261,10 @@ export class AndroidDevice extends ChannelOwner<channels.AndroidDeviceChannel> i
|
||||
async launchBrowser(options: types.BrowserContextOptions & { pkg?: string } = {}): Promise<BrowserContext> {
|
||||
const contextOptions = await prepareBrowserContextParams(this._platform, options);
|
||||
const result = await this._channel.launchBrowser(contextOptions);
|
||||
const context = BrowserContext.from(result.context) as BrowserContext;
|
||||
const context = BrowserContext.from(result.context);
|
||||
const selectors = this._android._playwright.selectors;
|
||||
selectors._contextsForSelectors.add(context);
|
||||
context.once(Events.BrowserContext.Close, () => selectors._contextsForSelectors.delete(context));
|
||||
await context._initializeHarFromOptions(options.recordHar);
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -76,12 +76,10 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
}
|
||||
|
||||
async _innerNewContext(options: BrowserContextOptions = {}, forReuse: boolean): Promise<BrowserContext> {
|
||||
options = {
|
||||
options = this._browserType._playwright.selectors._withSelectorOptions({
|
||||
...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);
|
||||
@ -117,6 +115,7 @@ export class Browser extends ChannelOwner<channels.BrowserChannel> implements ap
|
||||
context._logger = this._logger;
|
||||
context.tracing._tracesDir = this._options.tracesDir;
|
||||
this._browserType._contexts.add(context);
|
||||
this._browserType._playwright.selectors._contextsForSelectors.add(context);
|
||||
context.setDefaultTimeout(this._browserType._playwright._defaultContextTimeout);
|
||||
context.setDefaultNavigationTimeout(this._browserType._playwright._defaultContextNavigationTimeout);
|
||||
}
|
||||
|
||||
@ -458,6 +458,7 @@ export class BrowserContext extends ChannelOwner<channels.BrowserContextChannel>
|
||||
this._closingStatus = 'closed';
|
||||
this._browser?._contexts.delete(this);
|
||||
this._browser?._browserType._contexts.delete(this);
|
||||
this._browser?._browserType._playwright.selectors._contextsForSelectors.delete(this);
|
||||
this._disposeHarRouters();
|
||||
this.tracing._resetStackCounter();
|
||||
this.emit(Events.BrowserContext.Close, this);
|
||||
|
||||
@ -93,13 +93,11 @@ 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 = {
|
||||
options = this._playwright.selectors._withSelectorOptions({
|
||||
...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,
|
||||
@ -166,8 +164,7 @@ export class BrowserType extends ChannelOwner<channels.BrowserTypeChannel> imple
|
||||
connection.close();
|
||||
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
|
||||
}
|
||||
this._playwright.selectors._playwrights.add(playwright);
|
||||
connection.on('close', () => this._playwright.selectors._playwrights.delete(playwright));
|
||||
playwright.selectors = this._playwright.selectors;
|
||||
browser = Browser.from(playwright._initializer.preLaunchedBrowser!);
|
||||
browser._connectToBrowserType(this, {}, logger);
|
||||
browser._shouldCloseConnectionOnClose = true;
|
||||
|
||||
@ -31,6 +31,7 @@ import type * as api from '../../types/types';
|
||||
import type * as channels from '@protocol/channels';
|
||||
import type * as childProcess from 'child_process';
|
||||
import type { BrowserWindow } from 'electron';
|
||||
import type { Playwright } from './playwright';
|
||||
|
||||
type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHeaders'|'recordHar'|'colorScheme'|'acceptDownloads'> & {
|
||||
env?: Env,
|
||||
@ -44,6 +45,8 @@ type ElectronOptions = Omit<channels.ElectronLaunchOptions, 'env'|'extraHTTPHead
|
||||
type ElectronAppType = typeof import('electron');
|
||||
|
||||
export class Electron extends ChannelOwner<channels.ElectronChannel> implements api.Electron {
|
||||
_playwright!: Playwright;
|
||||
|
||||
static from(electron: channels.ElectronChannel): Electron {
|
||||
return (electron as any)._object;
|
||||
}
|
||||
@ -53,6 +56,7 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||
}
|
||||
|
||||
async launch(options: ElectronOptions = {}): Promise<ElectronApplication> {
|
||||
options = this._playwright.selectors._withSelectorOptions(options);
|
||||
const params: channels.ElectronLaunchParams = {
|
||||
...await prepareBrowserContextParams(this._platform, options),
|
||||
env: envObjectToArray(options.env ? options.env : this._platform.env),
|
||||
@ -60,6 +64,8 @@ export class Electron extends ChannelOwner<channels.ElectronChannel> implements
|
||||
timeout: new TimeoutSettings(this._platform).launchTimeout(options),
|
||||
};
|
||||
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
|
||||
this._playwright.selectors._contextsForSelectors.add(app._context);
|
||||
app.once(Events.ElectronApplication.Close, () => this._playwright.selectors._contextsForSelectors.delete(app._context));
|
||||
await app._context._initializeHarFromOptions(options.recordHar);
|
||||
app._context.tracing._tracesDir = options.tracesDir;
|
||||
return app;
|
||||
|
||||
@ -55,14 +55,15 @@ export class Playwright extends ChannelOwner<channels.PlaywrightChannel> {
|
||||
this.webkit = BrowserType.from(initializer.webkit);
|
||||
this.webkit._playwright = this;
|
||||
this._android = Android.from(initializer.android);
|
||||
this._android._playwright = this;
|
||||
this._electron = Electron.from(initializer.electron);
|
||||
this._electron._playwright = this;
|
||||
this._bidiChromium = BrowserType.from(initializer.bidiChromium);
|
||||
this._bidiChromium._playwright = this;
|
||||
this._bidiFirefox = BrowserType.from(initializer.bidiFirefox);
|
||||
this._bidiFirefox._playwright = this;
|
||||
this.devices = this._connection.localUtils()?.devices ?? {};
|
||||
this.selectors = new Selectors();
|
||||
this.selectors._playwrights.add(this);
|
||||
this.selectors = new Selectors(this._connection._platform);
|
||||
this.errors = { TimeoutError };
|
||||
(global as any)._playwrightInstance = this;
|
||||
}
|
||||
|
||||
@ -20,30 +20,35 @@ 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 { Playwright } from './playwright';
|
||||
import type { BrowserContext } from './browserContext';
|
||||
import type { Platform } from './platform';
|
||||
|
||||
export class Selectors implements api.Selectors {
|
||||
_playwrights = new Set<Playwright>();
|
||||
_selectorEngines: channels.SelectorEngine[] = [];
|
||||
_testIdAttributeName: string | undefined;
|
||||
private _platform: Platform;
|
||||
private _selectorEngines: channels.SelectorEngine[] = [];
|
||||
private _testIdAttributeName: string | undefined;
|
||||
readonly _contextsForSelectors = new Set<BrowserContext>();
|
||||
|
||||
constructor(platform: Platform) {
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
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 source = await evaluationScript(this._platform, script, undefined, false);
|
||||
const selectorEngine: channels.SelectorEngine = { ...options, name, source };
|
||||
for (const playwright of this._playwrights) {
|
||||
for (const context of playwright._allContexts())
|
||||
await context._channel.registerSelectorEngine({ selectorEngine });
|
||||
}
|
||||
for (const context of this._contextsForSelectors)
|
||||
await context._channel.registerSelectorEngine({ selectorEngine });
|
||||
this._selectorEngines.push(selectorEngine);
|
||||
}
|
||||
|
||||
setTestIdAttribute(attributeName: string) {
|
||||
this._testIdAttributeName = attributeName;
|
||||
setTestIdAttribute(attributeName);
|
||||
for (const playwright of this._playwrights) {
|
||||
for (const context of playwright._allContexts())
|
||||
context._channel.setTestIdAttributeName({ testIdAttributeName: attributeName }).catch(() => {});
|
||||
}
|
||||
for (const context of this._contextsForSelectors)
|
||||
context._channel.setTestIdAttributeName({ testIdAttributeName: attributeName }).catch(() => {});
|
||||
}
|
||||
|
||||
_withSelectorOptions<T>(options: T) {
|
||||
return { ...options, selectorEngines: this._selectorEngines, testIdAttributeName: this._testIdAttributeName };
|
||||
}
|
||||
}
|
||||
|
||||
@ -2528,6 +2528,8 @@ scheme.ElectronLaunchParams = tObject({
|
||||
strictSelectors: tOptional(tBoolean),
|
||||
timezoneId: tOptional(tString),
|
||||
tracesDir: tOptional(tString),
|
||||
selectorEngines: tOptional(tArray(tType('SelectorEngine'))),
|
||||
testIdAttributeName: tOptional(tString),
|
||||
});
|
||||
scheme.ElectronLaunchResult = tObject({
|
||||
electronApplication: tChannel(['ElectronApplication']),
|
||||
|
||||
4
packages/protocol/src/channels.d.ts
vendored
4
packages/protocol/src/channels.d.ts
vendored
@ -4397,6 +4397,8 @@ export type ElectronLaunchParams = {
|
||||
strictSelectors?: boolean,
|
||||
timezoneId?: string,
|
||||
tracesDir?: string,
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
};
|
||||
export type ElectronLaunchOptions = {
|
||||
executablePath?: string,
|
||||
@ -4430,6 +4432,8 @@ export type ElectronLaunchOptions = {
|
||||
strictSelectors?: boolean,
|
||||
timezoneId?: string,
|
||||
tracesDir?: string,
|
||||
selectorEngines?: SelectorEngine[],
|
||||
testIdAttributeName?: string,
|
||||
};
|
||||
export type ElectronLaunchResult = {
|
||||
electronApplication: ElectronApplicationChannel,
|
||||
|
||||
@ -3745,6 +3745,10 @@ Electron:
|
||||
strictSelectors: boolean?
|
||||
timezoneId: string?
|
||||
tracesDir: string?
|
||||
selectorEngines:
|
||||
type: array?
|
||||
items: SelectorEngine
|
||||
testIdAttributeName: string?
|
||||
|
||||
returns:
|
||||
electronApplication: ElectronApplication
|
||||
|
||||
@ -17,7 +17,6 @@
|
||||
import { test } from '@playwright/test';
|
||||
import type { TestModeName } from './testMode';
|
||||
import { DefaultTestMode, DriverTestMode } from './testMode';
|
||||
import * as playwrightLibrary from 'playwright-core';
|
||||
|
||||
export type TestModeWorkerOptions = {
|
||||
mode: TestModeName;
|
||||
@ -43,7 +42,6 @@ export const testModeTest = test.extend<TestModeTestFixtures, TestModeWorkerOpti
|
||||
'driver': new DriverTestMode(),
|
||||
}[mode];
|
||||
const playwright = await testMode.setup();
|
||||
(playwrightLibrary.selectors as any)._playwrights.add(playwright);
|
||||
await run(playwright);
|
||||
await testMode.teardown();
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user