Skip to content

Commit 76ac0f0

Browse files
committed
use middleware and update docs
1 parent 51f41e7 commit 76ac0f0

File tree

2 files changed

+158
-30
lines changed

2 files changed

+158
-30
lines changed

docs/config/extensions/playwright.mdx

Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ sidebarTitle: "playwright"
44
description: "Use the playwright build extension to use Playwright with Trigger.dev"
55
---
66

7-
If you are using Playwright, you should use the Playwright build extension.
7+
If you are using [Playwright](https://playwright.dev/), you should use the Playwright build extension.
88

99
- Automatically installs Playwright and required browser dependencies
1010
- Allows you to specify which browsers to install (chromium, firefox, webkit)
1111
- Supports headless or non-headless mode
1212
- Lets you specify the Playwright version, or auto-detects it
1313
- Installs only the dependencies needed for the selected browsers to optimize build time and image size
1414

15+
<Note>
16+
This extension only affects the build and deploy process, not the `dev` command.
17+
</Note>
18+
1519
You can use it for a simple Playwright setup like this:
1620

1721
```ts
@@ -29,18 +33,14 @@ export default defineConfig({
2933
});
3034
```
3135

32-
<Note>
33-
This extension only affects the build and deploy process, not the `dev` command.
34-
</Note>
35-
3636
### Options
3737

3838
- `browsers`: Array of browsers to install. Valid values: `"chromium"`, `"firefox"`, `"webkit"`. Default: `["chromium"]`.
3939
- `headless`: Run browsers in headless mode. Default: `true`. If set to `false`, a virtual display (Xvfb) will be set up automatically.
4040
- `version`: Playwright version to install. If not provided, the version will be auto-detected from your dependencies (recommended).
4141

4242
<Warning>
43-
Using a different version in your app than specified here will break things.
43+
Using a different version in your app than specified here will break things. We recommend not setting this option to automatically detect the version.
4444
</Warning>
4545

4646
### Custom browsers and version
@@ -91,13 +91,79 @@ The extension sets the following environment variables during the build:
9191
- `PLAYWRIGHT_SKIP_BROWSER_VALIDATION`: Set to `1` to skip browser validation at runtime
9292
- `DISPLAY`: Set to `:99` if `headless: false` (for Xvfb)
9393

94-
### Notes
94+
## Managing browser instances
9595

96-
- The extension installs only the dependencies required for the browsers you select, reducing build time and image size.
97-
- Playwright is installed globally in the build image.
98-
- The extension does not affect local development (`dev` command), only the build and deploy process.
99-
- Only specify the Playwright version in the options if automatic detection is not working.
96+
To prevent issues with waits and resumes, you can use middleware and locals to manage the browser instance. This will ensure the browser is available for the whole run, and is properly cleaned up on waits, resumes, and after the run completes.
10097

101-
<Note>
102-
For more information about Playwright, see the [official Playwright documentation](https://playwright.dev/).
103-
</Note>
98+
Here's an example using `chromium`, but you can adapt it for other browsers:
99+
100+
```ts
101+
import { logger, tasks, locals } from "@trigger.dev/sdk";
102+
import { chromium, type Browser } from "playwright";
103+
104+
// Create a locals key for the browser instance
105+
const PlaywrightBrowserLocal = locals.create<{ browser: Browser }>("playwright-browser");
106+
107+
export function getBrowser() {
108+
return locals.getOrThrow(PlaywrightBrowserLocal).browser;
109+
}
110+
111+
export function setBrowser(browser: Browser) {
112+
locals.set(PlaywrightBrowserLocal, { browser });
113+
}
114+
115+
tasks.middleware("playwright-browser", async ({ next }) => {
116+
// Launch the browser before the task runs
117+
const browser = await chromium.launch();
118+
setBrowser(browser);
119+
logger.log("[chromium]: Browser launched (middleware)");
120+
121+
try {
122+
await next();
123+
} finally {
124+
// Always close the browser after the task completes
125+
await browser.close();
126+
logger.log("[chromium]: Browser closed (middleware)");
127+
}
128+
});
129+
130+
tasks.onWait("playwright-browser", async () => {
131+
// Close the browser when the run is waiting
132+
const browser = getBrowser();
133+
await browser.close();
134+
logger.log("[chromium]: Browser closed (onWait)");
135+
});
136+
137+
tasks.onResume("playwright-browser", async () => {
138+
// Relaunch the browser when the run resumes
139+
// Note: You will have to have to manually get a new browser instance in the run function
140+
const browser = await chromium.launch();
141+
setBrowser(browser);
142+
logger.log("[chromium]: Browser launched (onResume)");
143+
});
144+
```
145+
146+
You can then use `getBrowser()` in your task's `run` function to access the browser instance:
147+
148+
```ts
149+
export const playwrightTestTask = task({
150+
id: "playwright-test",
151+
run: async () => {
152+
const browser = getBrowser();
153+
const page = await browser.newPage();
154+
await page.goto("https://google.com");
155+
await page.screenshot({ path: "screenshot.png" });
156+
await page.close();
157+
158+
// Waits will gracefully close the browser
159+
await wait.for({ seconds: 10 });
160+
161+
// On resume, we will re-launch the browser but you will have to manually get the new instance
162+
const newBrowser = getBrowser();
163+
const newPage = await newBrowser.newPage();
164+
await newPage.goto("https://playwright.dev");
165+
await newPage.screenshot({ path: "screenshot2.png" });
166+
await newPage.close();
167+
},
168+
});
169+
```
Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1-
import { logger, task } from "@trigger.dev/sdk/v3";
2-
import { chromium } from "playwright";
1+
import { logger, task, locals, tasks, wait } from "@trigger.dev/sdk";
2+
import { chromium, type Browser } from "playwright";
3+
34
/**
45
* Example task demonstrating Playwright browser automation with Trigger.dev
56
*
67
* To use other browsers (firefox, webkit):
78
* 1. Import them from playwright: `import { chromium, firefox, webkit } from "playwright";`
8-
* 2. Add them to the browserType array: `for (const browserType of [chromium, firefox, webkit])`
9+
* 2. Launch them in the middleware instead of chromium: `const browser = await firefox.launch();`
910
* 3. Configure the playwright extension in your project:
1011
* ```
1112
* // In your build configuration
1213
* import { playwright } from "@trigger.dev/core/v3/build";
1314
*
1415
* extensions: [
16+
* // Only add browsers your tasks will use
1517
* playwright({ browsers: ["chromium", "firefox", "webkit"] })
1618
* ]
1719
* ```
1820
*/
21+
1922
export const playwrightTestTask = task({
2023
id: "playwright-test",
2124
retry: {
@@ -26,23 +29,82 @@ export const playwrightTestTask = task({
2629

2730
logger.log("Starting Playwright automation task", { version: playwrightVersion });
2831

29-
for (const browserType of [chromium]) {
30-
const prefix = (msg: string) => `[${browserType.name()}]: ${msg}`;
32+
// Use the browser from locals
33+
const browser = getBrowser();
34+
const prefix = getPrefixFn(browser);
35+
36+
logger.log(prefix("Browser acquired from locals"));
37+
38+
// The onWait lifecycle hook will automatically close the browser
39+
// This ensures that checkpoint and restore will be successful
40+
await wait.for({ seconds: 10 });
41+
42+
// We have to get a new browser instance because the existing one was closed
43+
const newBrowser = getBrowser();
3144

32-
const browser = await browserType.launch();
33-
logger.log(prefix("Browser launched"));
45+
logger.log(prefix("New browser acquired from locals"));
3446

35-
const page = await browser.newPage();
36-
logger.log(prefix("New page created"));
47+
const page = await newBrowser.newPage();
48+
logger.log(prefix("New page created"));
3749

38-
await page.goto("https://google.com");
39-
logger.log(prefix("Navigated to google.com"));
50+
await page.goto("https://google.com");
51+
logger.log(prefix("Navigated to google.com"));
4052

41-
const screenshot = await page.screenshot({ path: "screenshot.png" });
42-
logger.log(prefix("Screenshot taken"), { size: screenshot.byteLength });
53+
const screenshot = await page.screenshot({ path: "screenshot.png" });
54+
logger.log(prefix("Screenshot taken"), { size: screenshot.byteLength });
4355

44-
await browser.close();
45-
logger.log(prefix("Browser closed"));
46-
}
56+
await page.close();
57+
logger.log(prefix("Page closed"));
4758
},
4859
});
60+
61+
const getPrefixFn = (browser: Browser) => {
62+
const browserType = browser.browserType();
63+
const browserName = browserType.name();
64+
return (msg: string) => `[${browserName}]: ${msg}`;
65+
};
66+
67+
// Locals key for Playwright browser
68+
const PlaywrightBrowserLocal = locals.create<{ browser: Browser }>("playwright-browser");
69+
70+
export function getBrowser() {
71+
return locals.getOrThrow(PlaywrightBrowserLocal).browser;
72+
}
73+
74+
export function setBrowser(browser: Browser) {
75+
locals.set(PlaywrightBrowserLocal, { browser });
76+
}
77+
78+
tasks.middleware("playwright-browser", async ({ ctx, payload, task, next }) => {
79+
// Only using chromium for now, can be extended for other browsers
80+
const browser = await chromium.launch();
81+
setBrowser(browser);
82+
83+
const prefix = getPrefixFn(browser);
84+
logger.log(prefix("Browser launched (middleware)"));
85+
86+
try {
87+
await next();
88+
} finally {
89+
await browser.close();
90+
logger.log(prefix("Browser closed (middleware)"));
91+
}
92+
});
93+
94+
tasks.onWait("playwright-browser", async ({ ctx, payload, task }) => {
95+
const browser = getBrowser();
96+
const prefix = getPrefixFn(browser);
97+
98+
await browser.close();
99+
logger.log(prefix("Browser closed (onWait)"));
100+
});
101+
102+
tasks.onResume("playwright-browser", async ({ ctx, payload, task }) => {
103+
// Only using chromium for now, can be extended for other browsers
104+
// Make sure this is the same browser as the one used in the middleware
105+
const browser = await chromium.launch();
106+
setBrowser(browser);
107+
108+
const prefix = getPrefixFn(browser);
109+
logger.log(prefix("Browser launched (onResume)"));
110+
});

0 commit comments

Comments
 (0)