{"componentChunkName":"component---src-gatsby-theme-chronoblog-templates-note-js","path":"/notes/browser-automation-playwright/","result":{"data":{"mdx":{"parent":{"__typename":"File","fields":{"gitLogLatestDate":"2026-05-17 15:56:10 +0200"}},"id":"20ba3c04-a310-5c76-85a7-20c13b77243e","excerpt":"Playwright is a headless browser library for automating browser tasks. Here's the list of some of the features (Playwright equivalents to…","frontmatter":{"title":"Browser automation with Playwright","date":"2026-05-17 13:40:00 UTC","job_ad":null,"job_ad_id":null,"job_ad_url":null,"tags":["playwright","node"],"cover":null},"fields":{"slug":"/notes/browser-automation-playwright/","readingTime":{"text":"4 min read"}},"body":"function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }\n\nfunction _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }\n\nfunction _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }\n\n/* @jsxRuntime classic */\n\n/* @jsx mdx */\nvar _frontmatter = {\n  \"title\": \"Browser automation with Playwright\",\n  \"date\": \"2026-05-17 13:40:00 UTC\",\n  \"tags\": [\"playwright\", \"node\"],\n  \"canonical_url\": \"https://sevic.dev/notes/browser-automation-playwright/\"\n};\nvar layoutProps = {\n  _frontmatter: _frontmatter\n};\nvar MDXLayout = \"wrapper\";\nreturn function MDXContent(_ref) {\n  var components = _ref.components,\n      props = _objectWithoutProperties(_ref, [\"components\"]);\n\n  return mdx(MDXLayout, _extends({}, layoutProps, props, {\n    components: components,\n    mdxType: \"MDXLayout\"\n  }), mdx(\"p\", null, \"Playwright is a headless browser library for automating browser tasks. Here's the list of some of the features (Playwright equivalents to the \", mdx(\"a\", _extends({\n    parentName: \"p\"\n  }, {\n    \"href\": \"https://sevic.dev/notes/browser-automation-puppeteer/\"\n  }), \"Puppeteer note\"), \"):\"), mdx(\"ul\", null, mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Turn off headless mode\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { chromium } from 'playwright';\\n\\nconst browser = await chromium.launch({\\n  headless: false\\n  // ...\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Resize the viewport to the window size\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const context = await browser.newContext({\\n  viewport: null\\n});\\nconst page = await context.newPage();\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Emulate screen how it's shown to the user via the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"emulateMedia\"), \" method\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.emulateMedia({ media: 'screen' });\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Save the page as a PDF file with a specified path, format, scale factor, and page range\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.pdf({\\n  path: 'path.pdf',\\n  format: 'A3',\\n  scale: 1,\\n  pageRanges: '1-2',\\n  printBackground: true\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Use preexisting user's credentials to skip logging in to some websites. The user data directory is a parent of the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Profile Path\"), \" value from the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"chrome://version\"), \" page. Use \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"launchPersistentContext\"), \" instead of \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"launch\"), \" + \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"newContext\"), \".\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { chromium } from 'playwright';\\n\\nconst context = await chromium.launchPersistentContext(\\n  'C:\\\\\\\\Users\\\\\\\\<USERNAME>\\\\\\\\AppData\\\\\\\\Local\\\\\\\\Google\\\\\\\\Chrome\\\\\\\\User Data',\\n  { headless: false }\\n);\\nconst page = context.pages()[0] ?? (await context.newPage());\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Use Chrome instance instead of Chromium via the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"channel\"), \" option. Close Chrome before running the script if the profile is in use.\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"import { chromium } from 'playwright';\\n\\nconst browser = await chromium.launch({\\n  channel: 'chrome'\\n  // ...\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Switch to the selected tab\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.bringToFront();\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Get value based on evaluation in the browser page\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const shouldPaginate = await page.evaluate(\\n  ([param1, param2]) => {\\n    // ...\\n  },\\n  [param1, param2]\\n);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Get HTML content from the specific element\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const html = await page.locator('.field--text').evaluate(\\n  (element) => element.outerHTML\\n);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Wait for a specific selector to be loaded. You can also provide a timeout in milliseconds\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.waitForSelector('.success', { timeout: 5000 });\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Manipulate with a specific element and click on some of the elements\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator('#header').evaluate(async (headerElement) => {\\n  // ...\\n  headerElement\\n    .querySelectorAll('svg')\\n    .item(13)\\n    .parentNode.click();\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Extend execution timeout for slow \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"evaluate\"), \" callbacks\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"page.setDefaultTimeout(0);\\n// or per action:\\nawait page.locator('#header').evaluate(async (headerElement) => {\\n  // ...\\n}, { timeout: 0 });\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Manipulate with multiple elements\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator('.some-class').evaluateAll(async (elements) => {\\n  // ...\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Wait for navigation (e.g., form submitting) to be done\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.waitForLoadState('networkidle', { timeout: 0 });\\n\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Or wait for navigation triggered by a click:\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await Promise.all([\\n  page.waitForNavigation({ waitUntil: 'networkidle', timeout: 0 }),\\n  page.click('button[type=\\\"submit\\\"]')\\n]);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Trigger hover event on some of the elements\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator('#header').hover();\\n\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Or dispatch a custom event in the page:\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator('#header').evaluate((headerElement) => {\\n  const hoverEvent = new MouseEvent('mouseover', {\\n    view: window,\\n    bubbles: true,\\n    cancelable: true\\n  });\\n\\n  headerElement.dispatchEvent(hoverEvent);\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Expose a function in the browser and use it in \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"evaluate\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"evaluateAll\"), \" callbacks (e.g., simulate typing using the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"window.type\"), \" function)\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.exposeFunction('type', async (selector, text, options) => {\\n  await page.locator(selector).type(text, options);\\n});\\n\\nawait page.locator('.some-class').evaluateAll(async (elements) => {\\n  // ...\\n  await window.type(selector, text, { delay: 0 });\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Press the \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"Enter\"), \" button after typing the input field value\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator(selector).fill(text);\\nawait page.locator(selector).press('Enter');\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Open a file chooser and select file for upload\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"const fileChooserPromise = page.waitForEvent('filechooser');\\nawait page.locator(selector).click();\\nconst fileChooser = await fileChooserPromise;\\n\\nconst filePath = `C:/Users/<USERNAME>/Downloads/test.jpeg`; // use \\\"/\\\" instead of \\\"\\\\\\\" in file path\\nawait fileChooser.setFiles(filePath);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Remove the value from the input field before typing the new one\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator(selector).fill(text);\\n\")), mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Or select all and replace:\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator(selector).click({ clickCount: 3 });\\nawait page.locator(selector).type(text, options);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Pass a variable into \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"evaluate\"), \" / \", mdx(\"inlineCode\", {\n    parentName: \"p\"\n  }, \"evaluateAll\"), \" callbacks via extra arguments\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.locator('#element').evaluate(\\n  async (element, customVariable) => {\\n    // ...\\n  },\\n  customVariable\\n);\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Mock response for the specific request\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.route(REDIRECTION_URL, async (route) => {\\n  await route.fulfill({\\n    contentType: 'text/html',\\n    status: 304,\\n    body: '<body></body>'\\n  });\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Intercept page redirections (via route) and open them in new tabs rather than following them in the same tab\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"await page.route(REDIRECTION_URL, async (route) => {\\n  const url = route.request().url();\\n\\n  await route.fulfill({\\n    contentType: 'text/html',\\n    status: 304,\\n    body: '<body></body>'\\n  });\\n\\n  const newPage = await context.newPage();\\n  await newPage.goto(url, { waitUntil: 'domcontentloaded', timeout: 0 });\\n  // ...\\n  await newPage.close();\\n});\\n\"))), mdx(\"li\", {\n    parentName: \"ul\"\n  }, mdx(\"p\", {\n    parentName: \"li\"\n  }, \"Intercept page response\"), mdx(\"pre\", {\n    parentName: \"li\"\n  }, mdx(\"code\", _extends({\n    parentName: \"pre\"\n  }, {\n    \"className\": \"language-js\"\n  }), \"page.on('response', async (response) => {\\n  if (response.url() === RESPONSE_URL) {\\n    if (response.status() === 200) {\\n      // ...\\n    }\\n    // ...\\n  }\\n});\\n\")))));\n}\n;\nMDXContent.isMDXComponent = true;"}},"pageContext":{"id":"20ba3c04-a310-5c76-85a7-20c13b77243e"}},"staticQueryHashes":["1961101537","2542493696"]}