Skip to content

Commit

Permalink
Allow puppeteer to connect to a remote chrome instance.
Browse files Browse the repository at this point in the history
  • Loading branch information
mhavelant authored and tweis committed May 15, 2024
1 parent 19c56d1 commit 5e9d4eb
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 14 deletions.
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,11 +747,12 @@ The [storageState](https://playwright.dev/docs/api/class-browsercontext#browser-
<!-- omit from toc -->
### Setting Puppeteer And Playwright Option Flags

Backstop sets two defaults for both Puppeteer and Playwright:
Backstop sets three defaults for both Puppeteer and Playwright:

```json
ignoreHTTPSErrors: true,
headless: <!!!config.debugWindow>
headless: <!!!config.debugWindow>,
remote: false
```

You can add more settings (or override the defaults) with the `engineOptions` property. (properties are merged). This is where headless mode can also be set to 'new', until "new headless mode" is less hacky and more supported by Playwright.
Expand All @@ -772,6 +773,53 @@ More info here:
* [Puppeteer on github](https://github.com/GoogleChrome/puppeteer).
* [Playwright on github](https://github.com/microsoft/playwright).

<!-- omit from toc -->
#### Running Puppeteer on an already running Chrome instance
```json
"engineOptions": {
"remote": true,
"remoteOptions": {
// Note: This ignoreHTTPSErrors is separate from the one in engineOptions, and no default is set for it by BackstopJS.
"ignoreHTTPSErrors": false,
"browserWSEndpoint": "ws://myremotechrome:9222/devtools/browser/e6447a74-d83f-4f4e-a8e9-388b5216e0c2"
}
}
```

For more available remoteOptions, check BrowserOptions and ConnectOptions from puppeteer.

Notes for remote mode:
- Anything outside of remoteOptions is ignored.
- Chrome has to be launched separately, and BackstopJS has to be able to connect to it.
- Args, headless, etc. have to be set for chrome when launching chrome.

<!-- omit from toc -->
#### Example setup with docker

1. Start a dockerized chrome instance
1. E.g `docker run -d -p 9222:9222 --cap-add=SYS_ADMIN justinribeiro/chrome-headless`
2. Note the output, it's the docker container ID, e.g `8d203d9598f89028f98389f50a92aab33e18699e502f3813ab584ee654a8ca8a`
2. Get the devtools web socket
1. Use `docker logs <container name or id>` (list these with `docker ps`)
1. E.g `docker logs 8d203d9598f89028f98389f50a92aab33e18699e502f3813ab584ee654a8ca8a`
2. E.g `docker logs loving_dijkstra`
2. Copy the web socket URL from the logs
1. E.g, if the logs say `DevTools listening on ws://0.0.0.0:9222/devtools/browser/e6447a74-d83f-4f4e-a8e9-388b5216e0c2`, then the URl is `ws://0.0.0.0:9222/devtools/browser/e6447a74-d83f-4f4e-a8e9-388b5216e0c2`
3. Create a new or edit an existing BackstopJS config and add these:
```json
"engineOptions": {
"remote": true,
"remoteOptions": {
"browserWSEndpoint": "ws://localhost:9222/devtools/browser/e6447a74-d83f-4f4e-a8e9-388b5216e0c2"
}
},
```

If everything went OK, now you should be able to use backstop commands as usual.
Notes:
- Since in this example chrome is running in a docker container, it won't see any files on your system, unless they are mounted to it.
This means `url: path/to/index.html` scenarios won't work. But making these work is more of a docker topic, not a BackstopJS one.

<!-- omit from toc -->
### Using Docker For Testing Across Different Environments

Expand Down Expand Up @@ -1195,6 +1243,11 @@ For all engines there is also the `debug` setting. This enables verbose console
"debug": true
```

Note, this does not work when using
```json
"remote": true
```

<!-- omit from toc -->
### Issues with Chrome-Headless in Docker

Expand Down
67 changes: 55 additions & 12 deletions core/util/runPuppet.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,54 @@ function loggerAction (action, color, message, ...rest) {
console[action](chalk[color](message), ...rest);
}

/**
*
* Launch the browser, or connect to it.
*
* @param {Object} puppeteerArgs
* @returns {Promise<Puppeteer.Browser>}
*/
async function obtainBrowser (puppeteerArgs) {
if (puppeteerArgs.remote === true) {
return puppeteer.connect(puppeteerArgs.remoteOptions);
}

return puppeteer.launch(puppeteerArgs);
}

/**
* Close the browser, or disconnect from it.
*
* @param {Puppeteer.Browser} browser
* @param {Object} puppeteerArgs
* @returns {Promise<*>}
*/
async function releaseBrowser (browser, puppeteerArgs) {
if (puppeteerArgs.remote === true) {
return browser.disconnect();
}

return browser.close();
}

/**
* Build the puppeteer args object.
*
* @param {Object} config
* @returns {Object}
*/
function buildPuppeteerArgs (config) {
return Object.assign(
{},
{
ignoreHTTPSErrors: true,
headless: config.debugWindow ? false : config?.engineOptions?.headless || 'new',
remote: false
},
config.engineOptions
);
}

async function processScenarioView (scenario, variantOrScenarioLabelSafe, scenarioLabelSafe, viewport, config, logger) {
const { scenarioDefaults = {} } = config;

Expand Down Expand Up @@ -76,16 +124,9 @@ async function processScenarioView (scenario, variantOrScenarioLabelSafe, scenar
const VP_W = viewport.width || viewport.viewport.width;
const VP_H = viewport.height || viewport.viewport.height;

const puppeteerArgs = Object.assign(
{},
{
ignoreHTTPSErrors: true,
headless: config.debugWindow ? false : config?.engineOptions?.headless || 'new'
},
config.engineOptions
);
const puppeteerArgs = buildPuppeteerArgs(config);

const browser = await puppeteer.launch(puppeteerArgs);
const browser = await obtainBrowser(puppeteerArgs);
const page = await browser.newPage();

await page.setViewport({ width: VP_W, height: VP_H });
Expand Down Expand Up @@ -286,7 +327,7 @@ async function processScenarioView (scenario, variantOrScenarioLabelSafe, scenar
error = e;
}
} else {
await browser.close();
await releaseBrowser(browser, puppeteerArgs);
}

if (error) {
Expand Down Expand Up @@ -355,6 +396,8 @@ async function delegateSelectors (
captureJobs.push(function () { return captureScreenshot(page, browser, null, selectorMap, config, captureList, viewport, logger); });
}

const puppeteerArgs = buildPuppeteerArgs(config);

return new Promise(function (resolve, reject) {
let job = null;
const errors = [];
Expand All @@ -378,10 +421,10 @@ async function delegateSelectors (
next();
}).then(async () => {
logger.log('green', 'x Close Browser');
await browser.close();
await releaseBrowser(browser, puppeteerArgs);
}).catch(async (err) => {
logger.log('red', err);
await browser.close();
await releaseBrowser(browser, puppeteerArgs);
}).then(_ => compareConfig);
}

Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@babel/core": "^7.23.6",
"@babel/preset-env": "^7.23.6",
"@babel/preset-react": "^7.23.3",
"@types/puppeteer": "^7.0.4",
"assert": "^2.1.0",
"babel-loader": "^9.1.3",
"backstop-twentytwenty": "^1.1.0",
Expand Down

0 comments on commit 5e9d4eb

Please sign in to comment.