1"use strict";(globalThis.webpackChunkjest_website||=[]).push([[4338],{43079(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>u,default:()=>h,frontMatter:()=>l,metadata:()=>s,toc:()=>p});const s=JSON.parse('{"id":"puppeteer","title":"Using with puppeteer","description":"With the Global Setup/Teardown and Async Test Environment APIs, Jest can work smoothly with puppeteer.","source":"@site/versioned_docs/version-30.5/Puppeteer.md","sourceDirName":".","slug":"/puppeteer","permalink":"/docs/puppeteer","draft":false,"unlisted":false,"editUrl":"https://github.com/jestjs/jest/edit/main/website/versioned_docs/version-30.5/Puppeteer.md","tags":[],"version":"30.5","lastUpdatedBy":"Simen Bekkhus","lastUpdatedAt":1787905187000,"frontMatter":{"id":"puppeteer","title":"Using with puppeteer"},"sidebar":"docs","previous":{"title":"Using with webpack","permalink":"/docs/webpack"},"next":{"title":"Using with MongoDB","permalink":"/docs/mongodb"}}');var r=t(62540),o=t(43023),a=t(49479),i=t(22491);const l={id:"puppeteer",title:"Using with puppeteer"},u=void 0,c={},p=[{value:"Use jest-puppeteer Preset",id:"use-jest-puppeteer-preset",level:2},{value:"Custom example without jest-puppeteer preset",id:"custom-example-without-jest-puppeteer-preset",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",h2:"h2",li:"li",ol:"ol",p:"p",pre:"pre",...(0,o.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(n.p,{children:["With the ",(0,r.jsx)(n.a,{href:"/docs/configuration#globalsetup-string",children:"Global Setup/Teardown"})," and ",(0,r.jsx)(n.a,{href:"/docs/configuration#testenvironment-string",children:"Async Test Environment"})," APIs, Jest can work smoothly with ",(0,r.jsx)(n.a,{href:"https://github.com/GoogleChrome/puppeteer",children:"puppeteer"}),"."]}),"\n",(0,r.jsx)(n.admonition,{type:"note",children:(0,r.jsxs)(n.p,{children:["Generating code coverage for test files using Puppeteer is currently not possible if your test uses ",(0,r.jsx)(n.code,{children:"page.$eval"}),", ",(0,r.jsx)(n.code,{children:"page.$$eval"})," or ",(0,r.jsx)(n.code,{children:"page.evaluate"})," as the passed function is executed outside of Jest's scope. Check out ",(0,r.jsx)(n.a,{href:"https://github.com/jestjs/jest/issues/7962#issuecomment-495272339",children:"issue #7962"})," on GitHub for a workaround."]})}),"\n",(0,r.jsx)(n.h2,{id:"use-jest-puppeteer-preset",children:"Use jest-puppeteer Preset"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.a,{href:"https://github.com/smooth-code/jest-puppeteer",children:"Jest Puppeteer"})," provides all required configuration to run your tests using Puppeteer."]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["First, install ",(0,r.jsx)(n.code,{children:"jest-puppeteer"})]}),"\n"]}),"\n",(0,r.jsxs)(a.A,{groupId:"npm2yarn",children:[(0,r.jsx)(i.A,{value:"npm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"npm install --save-dev jest-puppeteer\n"})})}),(0,r.jsx)(i.A,{value:"yarn",label:"Yarn",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"yarn add --dev jest-puppeteer\n"})})}),(0,r.jsx)(i.A,{value:"pnpm",label:"pnpm",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"pnpm add --save-dev jest-puppeteer\n"})})}),(0,r.jsx)(i.A,{value:"bun",label:"Bun",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-bash",children:"bun add --dev jest-puppeteer\n"})})})]}),"\n",(0,r.jsxs)(n.ol,{start:"2",children:["\n",(0,r.jsxs)(n.li,{children:["Specify preset in your ",(0,r.jsx)(n.a,{href:"/docs/configuration",children:"Jest configuration"}),":"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'{\n "preset": "jest-puppeteer"\n}\n'})}),"\n",(0,r.jsxs)(n.ol,{start:"3",children:["\n",(0,r.jsx)(n.li,{children:"Write your test"}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",children:"describe('Google', () => {\n beforeAll(async () => {\n await page.goto('https://google.com');\n });\n\n it('should be titled \"Google\"', async () => {\n await expect(page.title()).resolves.toMatch('Google');\n });\n});\n"})}),"\n",(0,r.jsxs)(n.p,{children:["There's no need to load any dependencies. Puppeteer's ",(0,r.jsx)(n.code,{children:"page"})," and ",(0,r.jsx)(n.code,{children:"browser"})," classes will automatically be exposed"]}),"\n",(0,r.jsxs)(n.p,{children:["See ",(0,r.jsx)(n.a,{href:"https://github.com/smooth-code/jest-puppeteer",children:"documentation"}),"."]}),"\n",(0,r.jsx)(n.h2,{id:"custom-example-without-jest-puppeteer-preset",children:"Custom example without jest-puppeteer preset"}),"\n",(0,r.jsx)(n.p,{children:"You can also hook up puppeteer from scratch. The basic
1idea is to:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"launch & file the websocket endpoint of puppeteer with Global Setup"}),"\n",(0,r.jsx)(n.li,{children:"connect to puppeteer from each Test Environment"}),"\n",(0,r.jsx)(n.li,{children:"close puppeteer with Global Teardown"}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"Here's an example of the GlobalSetup script"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="setup.js"',children:"const {mkdir, writeFile} = require('fs').promises;\nconst os = require('os');\nconst path = require('path');\nconst puppeteer = require('puppeteer');\n\nconst DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');\n\nmodule.exports = async function () {\n const browser = await puppeteer.launch();\n // store the browser instance so we can teardown it later\n // this global is only available in the teardown but not in TestEnvironments\n globalThis.__BROWSER_GLOBAL__ = browser;\n\n // use the file system to expose the wsEndpoint for TestEnvironments\n await mkdir(DIR, {recursive: true});\n await writeFile(path.join(DIR, 'wsEndpoint'), browser.wsEndpoint());\n};\n"})}),"\n",(0,r.jsx)(n.p,{children:"Then we need a custom Test Environment for puppeteer"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="puppeteer_environment.js"',children:"const {readFile} = require('fs').promises;\nconst os = require('os');\nconst path = require('path');\nconst puppeteer = require('puppeteer');\nconst NodeEnvironment = require('jest-environment-node').TestEnvironment;\n\nconst DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');\n\nclass PuppeteerEnvironment extends NodeEnvironment {\n constructor(config) {\n super(config);\n }\n\n async setup() {\n await super.setup();\n // get the wsEndpoint\n const wsEndpoint = await readFile(path.join(DIR, 'wsEndpoint'), 'utf8');\n if (!wsEndpoint) {\n throw new Error('wsEndpoint not found');\n }\n\n // connect to puppeteer\n this.global.__BROWSER_GLOBAL__ = await puppeteer.connect({\n browserWSEndpoint: wsEndpoint,\n });\n }\n\n async teardown() {\n if (this.global.__BROWSER_GLOBAL__) {\n this.global.__BROWSER_GLOBAL__.disconnect();\n }\n await super.teardown();\n }\n\n getVmContext() {\n return super.getVmContext();\n }\n}\n\nmodule.exports = PuppeteerEnvironment;\n"})}),"\n",(0,r.jsx)(n.p,{children:"Finally, we can close the puppeteer instance and clean-up the file"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="teardown.js"',children:"const fs = require('fs').promises;\nconst os = require('os');\nconst path = require('path');\n\nconst DIR = path.join(os.tmpdir(), 'jest_puppeteer_global_setup');\nmodule.exports = async function () {\n // close the browser instance\n await globalThis.__BROWSER_GLOBAL__.close();\n\n // clean-up the wsEndpoint file\n await fs.rm(DIR, {recursive: true, force: true});\n};\n"})}),"\n",(0,r.jsx)(n.p,{children:"With all the things set up, we can now write our tests like this:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",metastring:'title="test.js"',children:"const timeout = 5000;\n\ndescribe(\n '/ (Home Page)',\n () => {\n let page;\n beforeAll(async () => {\n page = await globalThis.__BROWSER_GLOBAL__.newPage();\n await page.goto('https://google.com');\n }, timeout);\n\n it('should load without error', async () => {\n const text = await page.evaluate(() => document.body.textContent);\n expect(text).toContain('google');\n });\n },\n timeout,\n);\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Finally, set ",(0,r.jsx)(n.code,{children:"jest.config.js"})," to read from these files. (The ",(0,r.jsx)(n.code,{children:"jest-puppeteer"})," preset does something like this under the hood.)"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-js",children:"module.exports = {\n globalSetup: './setup.js',\n globalTeardown: './teardown.js',\n testEnvironment: './puppeteer_environment.js',\n};\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Here's the code of ",(0,r.jsx)(n.a,{href:"https://github.com/xfumihiro/jest-puppeteer-example",children:"full working example"}),"."]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},22491(e,n,t){t.d(n,{A:()=>l});t(63696);var s=t(11750),r=t(75373);const o="tabItem_wHwb";var a=t(62540);function i({children:e,className:n,hidden:t}){return(0,a.jsx)("div",{role:"tabpanel",className:(0,s.A)(o,n),hidden:t,children:e})}function l({children:e,className:n,value:t}){const{selectedValue:s,lazy:o}=(0,r.uc)(),l=t===s;return!l&&o?null:(0,a.jsx)(i,{className:n,hidden:!l,children:e})}},49479(e,n,t){t.d(n,{A:()=>m});t(63696);var s=t(11750),r=t(53237),o=t(75373),a=t(90766),i=t(86681);const l="tabList_J5MA",u="tabItem_l0OV";var c=t(62540);function p({className:e}){const{selectedValue:n,selectValue:t,tabValues:r,block:i}=(0,o.uc)(),l=[],{blockElementScrollPositionUntilNextRender:p}=(0,a.a_)(),d=e=>{const s=e.currentTarget,o=l.indexOf(s),a=r[o].value;a!==n&&(p(s),t(a))},h=e=>{let n=null;switch(e.key){case"Enter":d(e);break;case"ArrowRight":{const t=l.indexOf(e.currentTarget)+1;n=l[t]??l[0];break}case"ArrowLeft":{const t=l.indexOf(e.currentTarget)-1;n=l[t]??l[l.length-1];break}}n?.focus()};return(0,c.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,s.A)("tabs",{"tabs--block":i},e),children:r.map(({value:e,label:t,attributes:r})=>(0,c.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{l.push(e)},onKeyDown:h,onClick:d,...r,className:(0,s.A)("tabs__item",u,r?.className,{"tabs__item--active":n===e}),children:t??e},e))})}function d({children:e}){return(0,c.jsx)("div",{className:"margin-top--md",children:e})}function h({className:e,children:n}){return(0,c.jsxs)("div",{className:(0,s.A)(r.G.tabs.container,"tabs-container",l),children:[(0,c.jsx)(p,{className:e}),(0,c.jsx)(d,{children:n})]})}function m(e){const n=(0,i.A)(),t=(0,o.OC)(e);return(0,c.jsx)(o.O_,{value:t,children:(0,c.jsx)(h,{className:e.className,children:(0,o.vT)(e.children)})},String(n))}},75373(e,n,t){t.d(n,{OC:()=>m,O_:()=>f,uc:()=>g,vT:()=>c});var s=t(63696),r=t(49519),o=t(14395),a=t(35043),i=t(94243),l=t(44544),u=t
1(62540);function c(e){return s.Children.toArray(e).filter(e=>"\n"!==e)}function p(e){const{values:n,children:t}=e;return(0,s.useMemo)(()=>{const e=n??function(e){return s.Children.toArray(e).flatMap(e=>{if(!e)return[];if((0,s.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];const n="string"==typeof e.type?e.type:e.type.name;throw new Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.\nIf you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)}).map(({props:{value:e,label:n,attributes:t,default:s}})=>({value:e,label:n,attributes:t,default:s}))}(t);return function(e){const n=(0,l.XI)(e,(e,n)=>e.value===n.value);if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map(e=>`'${e.value}'`).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e},[n,t])}function d({value:e,tabValues:n}){return n.some(n=>n.value===e)}function h({queryString:e=!1,groupId:n}){const t=(0,r.W6)(),o=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,a.aZ)(o),(0,s.useCallback)(e=>{if(!o)return;const n=new URLSearchParams(t.location.search);n.set(o,e),t.replace({...t.location,search:n.toString()})},[o,t])]}function m(e){const{defaultValue:n,queryString:t=!1,groupId:r}=e,a=p(e),[l,u]=(0,s.useState)(()=>function({defaultValue:e,tabValues:n}){if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!d({value:e,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map(e=>e.value).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const t=n.find(e=>e.default)??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:a})),[c,m]=h({queryString:t,groupId:r}),[j,g]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[t,r]=(0,i.Dv)(n);return[t,(0,s.useCallback)(e=>{n&&r.set(e)},[n,r])]}({groupId:r}),f=(()=>{const e=c??j;return d({value:e,tabValues:a})?e:null})();(0,o.A)(()=>{f&&u(f)},[f]);return{selectedValue:l,selectValue:(0,s.useCallback)(e=>{if(!d({value:e,tabValues:a}))throw new Error(`Can't select invalid tab value=${e}`);u(e),m(e),g(e)},[m,g,a]),tabValues:a,lazy:e.lazy??!1,block:e.block??!1}}const j=(0,s.createContext)(null);function g(){const e=s.useContext(j);if(!e)throw new Error("useTabsContext() must be used within a Tabs component");return e}function f(e){return(0,u.jsx)(j.Provider,{value:e.value,children:e.children})}},43023(e,n,t){t.d(n,{R:()=>a,x:()=>i});var s=t(63696);const r={},o=s.createContext(r);function a(e){const n=s.useContext(o);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function i(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:a(e.components),s.createElement(o.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.