Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 110 additions & 3 deletions docs/src/selenium-grid.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,36 @@ You don't have to change your code, just use your testing harness or [`method: B

When using Selenium Grid Hub, you can [skip browser downloads](./browsers.md#skip-browser-downloads).

Besides this, you can specify the url using the launch options (has a lower priority than the environment variable).

```js tab=js-test title="playwright.config.ts"
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'chromium',
use: {
launchOptions: {
selenium: {
url: 'http://localhost:4444/wd/hub',
},
},
},
},
],
});
```

```js tab=js-library
const { chromium } = require('playwright');
const browser = await chromium.launch({
selenium: {
url: 'http://localhost:4444/wd/hub',
},
});
```

### Passing additional capabilities

If your grid requires additional capabilities to be set (for example, you use an external service), you can set `SELENIUM_REMOTE_CAPABILITIES` environment variable to provide JSON-serialized capabilities.
Expand All @@ -64,6 +94,50 @@ SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 SELENIUM_REMOTE_CAPABILITIES="
SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 SELENIUM_REMOTE_CAPABILITIES="{'mygrid:options':{os:'windows',username:'John',password:'secure'}}" dotnet test
```

Also can be specified using the launch options (has a lower priority than the environment variable):

```js tab=js-test title="playwright.config.ts"
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'chromium',
use: {
launchOptions: {
selenium: {
url: 'http://localhost:4444/wd/hub',
capabilities: {
'mygrid:options': {
os: 'windows',
username: 'John',
password: 'secure',
}
},
},
},
},
},
],
});
```

```js tab=js-library
const { chromium } = require('playwright');
const browser = await chromium.launch({
selenium: {
url: 'http://localhost:4444/wd/hub',
capabilities: {
'mygrid:options': {
os: 'windows',
username: 'John',
password: 'secure',
},
},
},
});
```

### Passing additional headers

If your grid requires additional headers to be set (for example, you should provide authorization token to use browsers in your cloud), you can set `SELENIUM_REMOTE_HEADERS` environment variable to provide JSON-serialized headers.
Expand All @@ -84,6 +158,42 @@ SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 SELENIUM_REMOTE_HEADERS="{'Aut
SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 SELENIUM_REMOTE_HEADERS="{'Authorization':'OAuth 12345'}" dotnet test
```

Also can be specified using the launch options (has a lower priority than the environment variable):

```js tab=js-test title="playwright.config.ts"
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'chromium',
use: {
launchOptions: {
selenium: {
url: 'http://localhost:4444/wd/hub',
headers: {
'Authorization': 'OAuth 12345',
},
},
},
},
},
],
});
```

```js tab=js-library
const { chromium } = require('playwright');
const browser = await chromium.launch({
selenium: {
url: 'http://localhost:4444/wd/hub',
headers: {
'Authorization': 'OAuth 12345',
},
},
});
```

### Detailed logs

Run with `DEBUG=pw:browser*` environment variable to see how Playwright is connecting to Selenium Grid.
Expand All @@ -106,8 +216,6 @@ DEBUG=pw:browser* SELENIUM_REMOTE_URL=http://internal.grid:4444 dotnet test

If you file an issue, please include this log.



## Using Selenium Docker

One easy way to use Selenium Grid is to run official docker containers. Read more in [selenium docker images](https://github.com/SeleniumHQ/docker-selenium) documentation. For experimental arm images, see [docker-seleniarm](https://github.com/seleniumhq-community/docker-seleniarm).
Expand Down Expand Up @@ -188,7 +296,6 @@ SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 mvn test
SELENIUM_REMOTE_URL=http://<selenium-hub-ip>:4444 dotnet test
```


## Selenium 3

Internally, Playwright connects to the browser using [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) websocket. Selenium 4 exposes this capability, while Selenium 3 does not.
Expand Down
10 changes: 10 additions & 0 deletions packages/playwright-core/src/protocol/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,11 @@ scheme.BrowserTypeLaunchParams = tObject({
downloadsPath: tOptional(tString),
tracesDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean),
selenium: tOptional(tObject({
url: tOptional(tString),
capabilities: tOptional(tAny),
headers: tOptional(tAny),
})),
firefoxUserPrefs: tOptional(tAny),
slowMo: tOptional(tNumber),
});
Expand Down Expand Up @@ -520,6 +525,11 @@ scheme.BrowserTypeLaunchPersistentContextParams = tObject({
downloadsPath: tOptional(tString),
tracesDir: tOptional(tString),
chromiumSandbox: tOptional(tBoolean),
selenium: tOptional(tObject({
url: tOptional(tString),
capabilities: tOptional(tAny),
headers: tOptional(tAny),
})),
noDefaultViewport: tOptional(tBoolean),
viewport: tOptional(tObject({
width: tNumber,
Expand Down
3 changes: 2 additions & 1 deletion packages/playwright-core/src/server/browserType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ export abstract class BrowserType extends SdkObject {
const controller = new ProgressController(metadata, this);
controller.setLogName('browser');
const browser = await controller.run(progress => {
const seleniumHubUrl = (options as any).__testHookSeleniumRemoteURL || process.env.SELENIUM_REMOTE_URL;
const seleniumHubUrl = process.env.SELENIUM_REMOTE_URL || options.selenium?.url;

if (seleniumHubUrl)
return this._launchWithSeleniumHub(progress, seleniumHubUrl, options);
return this._innerLaunchWithRetries(progress, options, undefined, helper.debugProtocolLogger(protocolLogger)).catch(e => { throw this._rewriteStartupError(e); });
Expand Down
7 changes: 5 additions & 2 deletions packages/playwright-core/src/server/chromium/chromium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,11 @@ export class Chromium extends BrowserType {
const args = this._innerDefaultArgs(options);
args.push('--remote-debugging-port=0');
const isEdge = options.channel && options.channel.startsWith('msedge');

let desiredCapabilities = {
'browserName': isEdge ? 'MicrosoftEdge' : 'chrome',
[isEdge ? 'ms:edgeOptions' : 'goog:chromeOptions']: { args }
[isEdge ? 'ms:edgeOptions' : 'goog:chromeOptions']: { args },
...options.selenium?.capabilities
};

if (process.env.SELENIUM_REMOTE_CAPABILITIES) {
Expand All @@ -186,7 +188,8 @@ export class Chromium extends BrowserType {
desiredCapabilities = { ...desiredCapabilities, ...remoteCapabilities };
}

let headers: { [key: string]: string } = {};
let headers = { ...options.selenium?.headers };

if (process.env.SELENIUM_REMOTE_HEADERS) {
const remoteHeaders = parseSeleniumRemoteParams({ name: 'headers', value: process.env.SELENIUM_REMOTE_HEADERS }, progress);
if (remoteHeaders)
Expand Down
20 changes: 20 additions & 0 deletions packages/playwright-core/types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19607,6 +19607,26 @@ export interface LaunchOptions {
* If specified, traces are saved into this directory.
*/
tracesDir?: string;

/**
* Selenium settings
*/
selenium?: {
/**
* Url to connect to selenium server
*/
url?: string;

/**
* Browser desired capabilities. Check out the [WebDriver Protocol](https://w3c.github.io/webdriver/#capabilities) for more details.
*/
capabilities?: any;

/**
* Custom headers to pass into every request
*/
headers?: { [key: string]: string; };
},
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why build script remove my typings which must be there

}

export interface ConnectOverCDPOptions {
Expand Down
20 changes: 20 additions & 0 deletions packages/protocol/src/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,11 @@ export type BrowserTypeLaunchParams = {
downloadsPath?: string,
tracesDir?: string,
chromiumSandbox?: boolean,
selenium?: {
url?: string,
capabilities?: any,
headers?: any,
},
firefoxUserPrefs?: any,
slowMo?: number,
};
Expand All @@ -903,6 +908,11 @@ export type BrowserTypeLaunchOptions = {
downloadsPath?: string,
tracesDir?: string,
chromiumSandbox?: boolean,
selenium?: {
url?: string,
capabilities?: any,
headers?: any,
},
firefoxUserPrefs?: any,
slowMo?: number,
};
Expand Down Expand Up @@ -931,6 +941,11 @@ export type BrowserTypeLaunchPersistentContextParams = {
downloadsPath?: string,
tracesDir?: string,
chromiumSandbox?: boolean,
selenium?: {
url?: string,
capabilities?: any,
headers?: any,
},
noDefaultViewport?: boolean,
viewport?: {
width: number,
Expand Down Expand Up @@ -1002,6 +1017,11 @@ export type BrowserTypeLaunchPersistentContextOptions = {
downloadsPath?: string,
tracesDir?: string,
chromiumSandbox?: boolean,
selenium?: {
url?: string,
capabilities?: any,
headers?: any,
},
noDefaultViewport?: boolean,
viewport?: {
width: number,
Expand Down
6 changes: 6 additions & 0 deletions packages/protocol/src/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,12 @@ LaunchOptions:
downloadsPath: string?
tracesDir: string?
chromiumSandbox: boolean?
selenium:
type: object?
properties:
url: string?
capabilities: json?
headers: json?


ContextOptions:
Expand Down
42 changes: 30 additions & 12 deletions tests/library/browsertype-launch-selenium.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ test('selenium grid 3.141.59 standalone chromium', async ({ browserName, childPr
});
await waitForPort(port);

const __testHookSeleniumRemoteURL = `http://127.0.0.1:${port}/wd/hub`;
const browser = await browserType.launch({ __testHookSeleniumRemoteURL } as any);
const browser = await browserType.launch({
selenium: {
url: `http://127.0.0.1:${port}/wd/hub`,
},
});
const page = await browser.newPage();
await page.setContent('<title>Hello world</title><div>Get Started</div>');
await page.click('text=Get Started');
Expand Down Expand Up @@ -84,8 +87,11 @@ test('selenium grid 3.141.59 hub + node chromium', async ({ browserName, childPr
hub.waitForOutput('Registered a node'),
]);

const __testHookSeleniumRemoteURL = `http://127.0.0.1:${port}/wd/hub`;
const browser = await browserType.launch({ __testHookSeleniumRemoteURL } as any);
const browser = await browserType.launch({
selenium: {
url: `http://127.0.0.1:${port}/wd/hub`,
},
});
const page = await browser.newPage();
await page.setContent('<title>Hello world</title><div>Get Started</div>');
await page.click('text=Get Started');
Expand All @@ -108,8 +114,11 @@ test('selenium grid 4.8.3 standalone chromium', async ({ browserName, childProce
});
await waitForPort(port);

const __testHookSeleniumRemoteURL = `http://127.0.0.1:${port}/`;
const browser = await browserType.launch({ __testHookSeleniumRemoteURL } as any);
const browser = await browserType.launch({
selenium: {
url: `http://127.0.0.1:${port}/`,
},
});
const page = await browser.newPage();
await page.setContent('<title>Hello world</title><div>Get Started</div>');
await page.click('text=Get Started');
Expand All @@ -130,7 +139,6 @@ test('selenium grid 4.8.3 hub + node chromium', async ({ browserName, childProce
cwd: __dirname,
});
await waitForPort(port);
const __testHookSeleniumRemoteURL = `http://127.0.0.1:${port}/`;

const node = childProcess({
command: ['java', `-Dwebdriver.chrome.driver=${chromeDriver}`, '-jar', selenium_4_8_3, 'node', '--grid-url', `http://127.0.0.1:${port}`, '--port', String(port + 1)],
Expand All @@ -141,7 +149,11 @@ test('selenium grid 4.8.3 hub + node chromium', async ({ browserName, childProce
hub.waitForOutput('from DOWN to UP'),
]);

const browser = await browserType.launch({ __testHookSeleniumRemoteURL } as any);
const browser = await browserType.launch({
selenium: {
url: `http://127.0.0.1:${port}/`,
},
});
const page = await browser.newPage();
await page.setContent('<title>Hello world</title><div>Get Started</div>');
await page.click('text=Get Started');
Expand All @@ -163,8 +175,11 @@ test('selenium grid 4.8.3 standalone chromium broken driver', async ({ browserNa
});
await waitForPort(port);

const __testHookSeleniumRemoteURL = `http://127.0.0.1:${port}/`;
const error = await browserType.launch({ __testHookSeleniumRemoteURL } as any).catch(e => e);
const error = await browserType.launch({
selenium: {
url: `http://127.0.0.1:${port}/`,
},
}).catch(e => e);
expect(error.message).toContain(`Error connecting to Selenium at http://127.0.0.1:${port}/session: Could not start a new session`);

expect(grid.output).not.toContain('Starting ChromeDriver');
Expand All @@ -173,8 +188,11 @@ test('selenium grid 4.8.3 standalone chromium broken driver', async ({ browserNa
test('selenium grid 3.141.59 standalone non-chromium', async ({ browserName, browserType }, testInfo) => {
test.skip(browserName === 'chromium');

const __testHookSeleniumRemoteURL = `http://127.0.0.1:4444/wd/hub`;
const error = await browserType.launch({ __testHookSeleniumRemoteURL } as any).catch(e => e);
const error = await browserType.launch({
selenium: {
url: `http://127.0.0.1:4444/wd/hub`,
},
}).catch(e => e);
expect(error.message).toContain('Connecting to SELENIUM_REMOTE_URL is only supported by Chromium');
});

Expand Down