# Playwright Library and Advanced Browser Control

Course: [Playwright](https://www.devtutorials.ai/learn/playwright) · Section: Advanced Playwright and AI
Source: https://www.devtutorials.ai/learn/playwright/advanced/playwright-library-and-advanced-browser-control

> Playwright Test versus the Playwright Library, writing automation scripts with chromium.launch(), launch options, persistent contexts and connecting to running browsers, PDFs and full-page screenshots, controlling time with page.clock, running code in the page with evaluate, addInitScript and exposeFunction, waiting for page conditions with waitForFunction, and CDP sessions.

## Playwright Library and Advanced Browser Control

- So far, you used **Playwright Test**: the test runner with `test`, `expect`, fixtures and reports.
- Under it is the **Playwright Library**: the part that drives browsers. You can use it alone, to write scripts that are not tests.
- This lesson also covers tools for unusual jobs: controlling time, running your own code in the page, and talking to the browser at a lower level.

**Simple lesson:** Playwright Test is for testing. The Library is for any browser automation. Both share the same page API.

### A Real-World Example

- The sale banner shows a countdown to midnight. Its text changes every moment, so how can a test check it?
- `page.clock.setFixedTime()` stops the page's clock at a chosen moment. Now the text is always the same.

- The page's `Date` now returns 20:00 every time. Timers still run, but the time they read never moves.

### What You Will Learn

1. Playwright Test versus the Playwright Library.
2. Writing automation scripts: launch, context, page, close.
3. Launch options, persistent profiles and connecting to a running browser.
4. Controlling time with `page.clock`.
5. Running code in the page: `evaluate`, `addInitScript`, `exposeFunction` and `waitForFunction`.
6. CDP sessions and other low-level tools.

## Playwright Test or the Library

### Two Packages

| | Playwright Test | Playwright Library |
|---|---|---|
| Package | `@playwright/test` | `playwright` |
| You get | `test`, `expect`, fixtures, config, reporters, retries, parallel workers | `chromium`, `firefox`, `webkit`, and the browser API |
| You manage | Nothing, the runner opens and closes browsers | Launching, contexts, closing, errors |
| Use it for | Testing web apps | Scraping, PDFs, screenshots, bots, custom tools |

- Both use the same `Page`, `Locator` and `BrowserContext` APIs. Everything you learned works in both.
- Playwright Test includes the Library, so test projects need only `@playwright/test`.

### A Library Script

- A script launches a browser, makes a context and a page, does its job, and closes the browser.
- Run it with Node.js: `node save-prices.mjs`. There is no `test()` and no `expect()`.

```ts
// save-prices.ts
import { chromium } from 'playwright';

const browser = await chromium.launch();
const context = await browser.newContext({ locale: 'en-IN' });
const page = await context.newPage();

await page.goto('https://practice.devtutorials.ai/products');
const names = await page.getByTestId('product-card').getByRole('heading').allTextContents();
const prices = await page.locator('.price').allTextContents();
console.log(names.map((name, i) => `${name}: ${prices[i]}`).join('\n'));

await page.pdf({ path: 'products.pdf', format: 'A4' }); // Chromium only
await page.screenshot({ path: 'products.png', fullPage: true });

await browser.close();
```

- Always close the browser, even after an error. Wrap the work in `try { ... } finally { await browser.close(); }`.
- `page.pdf()` works only in Chromium, in headless mode.

The lab runs Playwright Test only, so Library scripts on this page are plain code. Copy them into a project with `npm install playwright` and run them with Node.js.

**Simple lesson:** Library scripts do the setup and cleanup the test runner does for you.

## Launching Browsers

### Launch Options

- `chromium.launch({ headless: false })` shows the window. `slowMo: 250` waits after each action.
- `channel: 'chrome'` or `channel: 'msedge'` uses the installed Google Chrome or Microsoft Edge.
- In Playwright Test, the same options go in `use: { launchOptions: { ... } }` or `use: { channel: 'chrome' }`.

### Persistent Contexts

- Normal contexts are like incognito windows: empty every time.
- `chromium.launchPersistentContext('./profile')` keeps cookies, storage and extensions in a folder, like a real user profile.
- Use it for **browser extensions**, or for tools that must stay logged in between runs.

```ts
const context = await chromium.launchPersistentContext('./my-profile', {
  headless: false,
  args: ['--load-extension=./my-extension'],
});
const page = context.pages()[0] ?? await context.newPage();
```

### Connecting to a Browser That Is Already Running

- `chromium.connectOverCDP('http://localhost:9222')` attaches to a Chrome started with `--remote-debugging-port=9222`.
- `browserType.connect(wsEndpoint)` connects to a Playwright browser server, for example a cloud browser service.
- Remote browsers let a small CI machine drive many browsers somewhere else.

## Controlling Time

### Why Fake Time

- Pages show clocks, countdowns, "posted 5 minutes ago" text and session timeouts.
- Waiting for real time is slow, and the text changes on every run.
- `page.clock` replaces the page's `Date`, `setTimeout` and `setInterval` with a clock you control.

### The Clock Methods

- `setFixedTime(time)`: `Date` always returns this time. Timers still run.
- `install({ time })`: start a fake clock at this time. Time flows, but you can move it.
- `pauseAt(time)`: jump to a time and stop there.
- `fastForward(ticks)`: jump ahead. Each due timer fires once. Ticks are milliseconds, or text like `'30:00'`.
- `runFor(ticks)`: move ahead step by step, firing every timer on the way.
- `resume()`: let time flow again. Call clock methods before `goto()` when the page reads the time as it loads.

<figure className="plw-fig">
<svg viewBox="0 0 640 170" role="img" aria-labelledby="fig-plw-clock" style={{ width: "100%", maxWidth: 640, height: "auto" }}>
  <title id="fig-plw-clock">Animation: a timeline from 20:00 to 21:30. pauseAt jumps to 21:00 and stops. fastForward jumps 30 minutes to 21:30. The countdown text changes from 04:00:00 to 03:00:00 to 02:30:00</title>
  <line x1="40" y1="80" x2="600" y2="80" style={{ stroke: "var(--ink2)", strokeWidth: 2 }} />
  <g style={{ fontFamily: "var(--mono)", fontSize: 12 }}>
    <text x="40" y="105" textAnchor="middle" style={{ fill: "var(--ink2)" }}>20:00</text>
    <text x="413" y="105" textAnchor="middle" style={{ fill: "var(--ink2)" }}>21:00</text>
    <text x="600" y="105" textAnchor="middle" style={{ fill: "var(--ink2)" }}>21:30</text>
  </g>
  <circle cx="40" cy="80" r="9" style={{ fill: "var(--plw-purple)" }}>
    <animate attributeName="cx" values="40;40;413;413;600;600" keyTimes="0;0.25;0.35;0.6;0.7;1" dur="8s" repeatCount="indefinite" />
  </circle>
  <g style={{ fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>
    <text x="320" y="40" textAnchor="middle" style={{ fill: "var(--plw-purple)" }}>Countdown: 04:00:00<animate attributeName="opacity" values="1;1;0;0;1" keyTimes="0;0.3;0.32;0.98;1" dur="8s" repeatCount="indefinite" /></text>
    <text x="320" y="40" textAnchor="middle" opacity="0" style={{ fill: "var(--plw-purple)" }}>pauseAt(21:00) → 03:00:00<animate attributeName="opacity" values="0;0;1;1;0;0" keyTimes="0;0.32;0.35;0.62;0.65;1" dur="8s" repeatCount="indefinite" /></text>
    <text x="320" y="40" textAnchor="middle" opacity="0" style={{ fill: "var(--plw-purple)" }}>fastForward('30:00') → 02:30:00<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.65;0.7;0.97;1" dur="8s" repeatCount="indefinite" /></text>
  </g>
  <text x="320" y="145" textAnchor="middle" style={{ fill: "var(--ink2)", fontFamily: "var(--sans)", fontSize: 12 }}>The page's time moves only when the test says so.</text>
</svg>
<figcaption>With a fake clock, a test can jump hours ahead in milliseconds.</figcaption>
</figure>

`page.clock` changes time only inside the page. Your test code and `expect` timeouts still use real time. Check the page's state with web-first assertions, as above.

**Simple lesson:** `setFixedTime` freezes the date. `install` plus `pauseAt`, `fastForward` and `runFor` move it.

## Running Code in the Page

### page.evaluate()

- `page.evaluate(fn, arg)` runs a function **inside the page** and returns the result to the test.
- The function cannot see your test's variables. Pass values as the second argument.
- Results must be plain data: strings, numbers, arrays and objects. DOM elements cannot come back.

- Prefer locators and assertions for what users see. Use `evaluate` for data users cannot see, like storage or app state.

### addInitScript()

- `page.addInitScript(fn)` runs code **before** the page's own scripts, on every page load.
- Use it to set up state, for example a cart in local storage, a feature flag, or a fake API.

### exposeFunction() and waitForFunction()

- `page.exposeFunction(name, fn)` adds a function to the page's `window` that runs in your **test**. The page can call back into Node.js, for example to hash data or read a file.
- `page.waitForFunction(fn)` waits until a function in the page returns a truthy value. Use it for conditions no locator can describe.

**Simple lesson:** `evaluate` reads the page, `addInitScript` prepares it, `exposeFunction` lets it call back.

## Lower-Level Tools

### CDP Sessions

- Chromium speaks the **Chrome DevTools Protocol** (CDP). It can do things Playwright has no API for, such as CPU throttling.
- `context.newCDPSession(page)` opens a session. It works only in Chromium.

```ts
test('slow CPU', async ({ page }) => {
  const client = await page.context().newCDPSession(page);
  await client.send('Emulation.setCPUThrottlingRate', { rate: 4 });
  await page.goto('/products');
  // ... measure how the page behaves on a slow phone
});
```

### More Places Playwright Runs

- **Electron** desktop apps: `_electron.launch()` from `playwright`.
- **Android**: `_android` drives Chrome on Android devices (experimental).
- Other languages: Playwright also has Python, Java and .NET versions with the same API names.

Reach for low-level tools last. Locators, web-first assertions, routes and the clock cover almost every test. Lower-level code is harder to read and often works in one browser only.

**Simple lesson:** CDP and friends exist for rare needs. Most tests never need them.

## Try It in Your Project

- These steps take the lesson from the lab to your own app.
- Find one page in your app that shows dates or countdowns and test it with `page.clock`.
- Use `addInitScript()` to set feature flags or test data before load.
- Write one small Library script that takes screenshots of your key pages.

**Simple lesson:** use one idea from this lesson on your own app this week.

## TL;DR

- `@playwright/test` is for testing. `playwright` (the Library) is for scripts. Both share the page API.
- Scripts launch a browser, make a context and page, and must close the browser.
- `launchPersistentContext` keeps a profile. `connectOverCDP` attaches to a running Chrome.
- `page.clock` fakes time: `setFixedTime`, `install`, `pauseAt`, `fastForward`, `runFor`.
- `evaluate` runs code in the page. `addInitScript` runs before page scripts. `exposeFunction` calls back to the test.
- CDP sessions, Electron and Android cover rare needs.

## Common mistakes

| Mistake | What happens | Fix |
|---|---|---|
| A Library script that never closes the browser | The script hangs or leaks processes | `try { ... } finally { await browser.close() }` |
| Using a test variable inside `evaluate` | `ReferenceError` in the page | Pass it as the second argument |
| Returning a DOM element from `evaluate` | The result is empty or an error | Return plain data, or use a locator |
| Setting the clock after the page read the time | The page shows the real time | Call clock methods before `goto()` |
| Using CDP for normal checks | Chromium-only, hard-to-read tests | Use locators and assertions |

## Check Yourself

## Exercises

## What to Remember

- Playwright Test for tests, the Library for scripts. Same page API.
- Scripts must launch and close browsers themselves.
- `page.clock` makes time-based pages testable.
- `evaluate`, `addInitScript` and `exposeFunction` reach inside the page.
- Low-level tools are for rare needs.

Next: **AI-Assisted Testing with Agents, MCP, and CLI**
