CINXE.COM
The testing environment | web.dev
<!doctype html> <html lang="en" dir="ltr"> <head> <meta name="google-signin-client-id" content="157101835696-ooapojlodmuabs2do2vuhhnf90bccmoi.apps.googleusercontent.com"> <meta name="google-signin-scope" content="profile email https://www.googleapis.com/auth/developerprofiles https://www.googleapis.com/auth/developerprofiles.award"> <meta property="og:site_name" content="web.dev"> <meta property="og:type" content="website"><meta name="theme-color" content="#3740ff"><meta charset="utf-8"> <meta content="IE=Edge" http-equiv="X-UA-Compatible"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="manifest" href="/_pwa/web/manifest.json" crossorigin="use-credentials"> <link rel="preconnect" href="//www.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.gstatic.com" crossorigin> <link rel="preconnect" href="//fonts.googleapis.com" crossorigin> <link rel="preconnect" href="//apis.google.com" crossorigin> <link rel="preconnect" href="//www.google-analytics.com" crossorigin><link rel="stylesheet" href="//fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"> <link rel="stylesheet" href="//fonts.googleapis.com/css2?family=Material+Icons&family=Material+Symbols+Outlined&display=block"><link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/app.css"> <link rel="stylesheet" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/css/dark-theme.css" disabled> <link rel="shortcut icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png"> <link rel="apple-touch-icon" href="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/touchicon-180.png"><link rel="canonical" href="https://web.dev/learn/testing/get-started/testing-environment"><link rel="search" type="application/opensearchdescription+xml" title="web.dev" href="https://web.dev/s/opensearch.xml"> <link rel="alternate" hreflang="en" href="https://web.dev/learn/testing/get-started/testing-environment" /><link rel="alternate" hreflang="x-default" href="https://web.dev/learn/testing/get-started/testing-environment" /><link rel="alternate" hreflang="ar" href="https://web.dev/learn/testing/get-started/testing-environment?hl=ar" /><link rel="alternate" hreflang="bn" href="https://web.dev/learn/testing/get-started/testing-environment?hl=bn" /><link rel="alternate" hreflang="zh-Hans" href="https://web.dev/learn/testing/get-started/testing-environment?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant" href="https://web.dev/learn/testing/get-started/testing-environment?hl=zh-tw" /><link rel="alternate" hreflang="fa" href="https://web.dev/learn/testing/get-started/testing-environment?hl=fa" /><link rel="alternate" hreflang="fr" href="https://web.dev/learn/testing/get-started/testing-environment?hl=fr" /><link rel="alternate" hreflang="de" href="https://web.dev/learn/testing/get-started/testing-environment?hl=de" /><link rel="alternate" hreflang="he" href="https://web.dev/learn/testing/get-started/testing-environment?hl=he" /><link rel="alternate" hreflang="hi" href="https://web.dev/learn/testing/get-started/testing-environment?hl=hi" /><link rel="alternate" hreflang="id" href="https://web.dev/learn/testing/get-started/testing-environment?hl=id" /><link rel="alternate" hreflang="it" href="https://web.dev/learn/testing/get-started/testing-environment?hl=it" /><link rel="alternate" hreflang="ja" href="https://web.dev/learn/testing/get-started/testing-environment?hl=ja" /><link rel="alternate" hreflang="ko" href="https://web.dev/learn/testing/get-started/testing-environment?hl=ko" /><link rel="alternate" hreflang="pl" href="https://web.dev/learn/testing/get-started/testing-environment?hl=pl" /><link rel="alternate" hreflang="pt-BR" href="https://web.dev/learn/testing/get-started/testing-environment?hl=pt-br" /><link rel="alternate" hreflang="ru" href="https://web.dev/learn/testing/get-started/testing-environment?hl=ru" /><link rel="alternate" hreflang="es-419" href="https://web.dev/learn/testing/get-started/testing-environment?hl=es-419" /><link rel="alternate" hreflang="th" href="https://web.dev/learn/testing/get-started/testing-environment?hl=th" /><link rel="alternate" hreflang="tr" href="https://web.dev/learn/testing/get-started/testing-environment?hl=tr" /><link rel="alternate" hreflang="vi" href="https://web.dev/learn/testing/get-started/testing-environment?hl=vi" /><link rel="alternate" hreflang="en-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment" /><link rel="alternate" hreflang="x-default" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment" /><link rel="alternate" hreflang="ar-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=ar" /><link rel="alternate" hreflang="bn-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=bn" /><link rel="alternate" hreflang="zh-Hans-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=zh-cn" /><link rel="alternate" hreflang="zh-Hant-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=zh-tw" /><link rel="alternate" hreflang="fa-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=fa" /><link rel="alternate" hreflang="fr-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=fr" /><link rel="alternate" hreflang="de-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=de" /><link rel="alternate" hreflang="he-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=he" /><link rel="alternate" hreflang="hi-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=hi" /><link rel="alternate" hreflang="id-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=id" /><link rel="alternate" hreflang="it-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=it" /><link rel="alternate" hreflang="ja-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=ja" /><link rel="alternate" hreflang="ko-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=ko" /><link rel="alternate" hreflang="pl-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=pl" /><link rel="alternate" hreflang="pt-BR-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=pt-br" /><link rel="alternate" hreflang="ru-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=ru" /><link rel="alternate" hreflang="es-419-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=es-419" /><link rel="alternate" hreflang="th-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=th" /><link rel="alternate" hreflang="tr-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=tr" /><link rel="alternate" hreflang="vi-cn" href="https://web.developers.google.cn/learn/testing/get-started/testing-environment?hl=vi" /><title>The testing environment | web.dev</title> <meta property="og:title" content="The testing environment | web.dev"><meta name="description" content="Learn to use runtime tools and browser emulation for testing."> <meta property="og:description" content="Learn to use runtime tools and browser emulation for testing."><meta property="og:url" content="https://web.dev/learn/testing/get-started/testing-environment"><meta property="og:image" content="https://web.dev/static/images/social-wide.jpg"> <meta property="og:image:width" content="1200"> <meta property="og:image:height" content="675"><meta property="og:locale" content="en"><meta name="twitter:card" content="summary"><meta name="twitter:image" content="https://web.dev/static/images/social-wide.jpg"><meta name="twitter:site" content="@ChromiumDev"> <link rel="stylesheet" href="/extras.css"></head> <body class="" template="page" theme="web-theme" type="course" appearance layout="docs" display-toc pending> <devsite-progress type="indeterminate" id="app-progress"></devsite-progress> <section class="devsite-wrapper"> <devsite-cookie-notification-bar></devsite-cookie-notification-bar><devsite-header role="banner"> <div class="devsite-header--inner nocontent"> <div class="devsite-top-logo-row-wrapper-wrapper"> <div class="devsite-top-logo-row-wrapper"> <div class="devsite-top-logo-row"> <button type="button" id="devsite-hamburger-menu" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Navigation menu button" visually-hidden aria-label="Open menu"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> <div class="devsite-top-logo-row-middle"> <div class="devsite-header-upper-tabs"> <devsite-tabs class="upper-tabs"> <nav class="devsite-tabs-wrapper" aria-label="Upper tabs"> <tab > <a href="https://web.dev/about" track-metadata-eventdetail="https://web.dev/about" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - about" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" > About </a> </tab> <tab > <a href="https://web.dev/html" track-metadata-eventdetail="https://web.dev/html" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - html" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" > HTML </a> </tab> <tab > <a href="https://web.dev/css" track-metadata-eventdetail="https://web.dev/css" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - css" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" > CSS </a> </tab> <tab > <a href="https://web.dev/javascript" track-metadata-eventdetail="https://web.dev/javascript" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - javascript" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" > JavaScript </a> </tab> <tab > <a href="https://web.dev/blog" track-metadata-eventdetail="https://web.dev/blog" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - blog" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" > Blog </a> </tab> <tab class="devsite-active"> <a href="https://web.dev/learn" track-metadata-eventdetail="https://web.dev/learn" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - learn" track-metadata-module="primary nav" aria-label="Learn, selected" data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" > Learn </a> </tab> <tab > <a href="https://web.dev/explore" track-metadata-eventdetail="https://web.dev/explore" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - explore" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" > Explore </a> </tab> <tab > <a href="https://web.dev/patterns" track-metadata-eventdetail="https://web.dev/patterns" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - patterns" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" > Patterns </a> </tab> <tab > <a href="https://web.dev/case-studies" track-metadata-eventdetail="https://web.dev/case-studies" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - case studies" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" > Case studies </a> </tab> </nav> </devsite-tabs> </div> <devsite-search enable-signin enable-search enable-suggestions enable-query-completion project-name="web.dev" tenant-name="web.dev" > <form class="devsite-search-form" action="https://web.dev/s/results" method="GET"> <div class="devsite-search-container"> <button type="button" search-open class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Open search"></button> <div class="devsite-searchbox"> <input aria-activedescendant="" aria-autocomplete="list" aria-label="Search" aria-expanded="false" aria-haspopup="listbox" autocomplete="off" class="devsite-search-field devsite-search-query" name="q" placeholder="Search" role="combobox" type="text" value="" > <div class="devsite-search-image material-icons" aria-hidden="true"> </div> <div class="devsite-search-shortcut-icon-container" aria-hidden="true"> <kbd class="devsite-search-shortcut-icon">/</kbd> </div> </div> </div> </form> <button type="button" search-close class="devsite-search-button devsite-header-icon-button button-flat material-icons" aria-label="Close search"></button> </devsite-search> </div> <devsite-appearance-selector></devsite-appearance-selector> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> <devsite-user enable-profiles id="devsite-user"> <span class="button devsite-top-button" aria-hidden="true" visually-hidden>Sign in</span> </devsite-user> </div> </div> </div> <div class="devsite-collapsible-section "> <div class="devsite-header-background"> <div class="devsite-product-id-row" > <div class="devsite-product-description-row"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> <a href="https://web.dev/learn" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Lower Header" data-value="1" track-type="globalNav" track-name="breadcrumb" track-metadata-position="1" track-metadata-eventdetail="" > Learn </a> </li> </ul> </div> </div> <div class="devsite-doc-set-nav-row"> <devsite-tabs class="lower-tabs"> <nav class="devsite-tabs-wrapper" aria-label="Lower tabs"> <tab > <a href="https://web.dev/learn/privacy" track-metadata-eventdetail="https://web.dev/learn/privacy" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - privacy" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Privacy" track-name="privacy" > Privacy </a> </tab> <tab > <a href="https://web.dev/learn/accessibility" track-metadata-eventdetail="https://web.dev/learn/accessibility" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - accessibility" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Accessibility" track-name="accessibility" > Accessibility </a> </tab> <tab > <a href="https://web.dev/learn/html" track-metadata-eventdetail="https://web.dev/learn/html" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - html" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" > HTML </a> </tab> <tab > <a href="https://web.dev/learn/images" track-metadata-eventdetail="https://web.dev/learn/images" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - images" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Images" track-name="images" > Images </a> </tab> <tab > <a href="https://web.dev/learn/design" track-metadata-eventdetail="https://web.dev/learn/design" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - responsive design" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Responsive Design" track-name="responsive design" > Responsive Design </a> </tab> <tab > <a href="https://web.dev/learn/forms" track-metadata-eventdetail="https://web.dev/learn/forms" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - forms" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Forms" track-name="forms" > Forms </a> </tab> <tab > <a href="https://web.dev/learn/pwa" track-metadata-eventdetail="https://web.dev/learn/pwa" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - pwa" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: PWA" track-name="pwa" > PWA </a> </tab> <tab > <a href="https://web.dev/learn/css" track-metadata-eventdetail="https://web.dev/learn/css" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - css" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" > CSS </a> </tab> <tab > <a href="https://web.dev/learn/performance" track-metadata-eventdetail="https://web.dev/learn/performance" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - performance" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: Performance" track-name="performance" > Performance </a> </tab> <tab class="devsite-active"> <a href="https://web.dev/learn/testing" track-metadata-eventdetail="https://web.dev/learn/testing" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - testing" track-metadata-module="primary nav" aria-label="Testing, selected" data-category="Site-Wide Custom Events" data-label="Tab: Testing" track-name="testing" > Testing </a> </tab> <tab > <a href="https://web.dev/learn/javascript" track-metadata-eventdetail="https://web.dev/learn/javascript" class="devsite-tabs-content gc-analytics-event " track-type="nav" track-metadata-position="nav - javascript" track-metadata-module="primary nav" data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" > JavaScript </a> </tab> </nav> </devsite-tabs> </div> </div> </div> </div> </devsite-header> <devsite-book-nav scrollbars > <div class="devsite-book-nav-filter" > <span class="filter-list-icon material-icons" aria-hidden="true"></span> <input type="text" placeholder="Filter" aria-label="Type to filter" role="searchbox"> <span class="filter-clear-button hidden" data-title="Clear filter" aria-label="Clear filter" role="button" tabindex="0"></span> </div> <nav class="devsite-book-nav devsite-nav nocontent" aria-label="Side menu"> <div class="devsite-mobile-header"> <button type="button" id="devsite-close-nav" class="devsite-header-icon-button button-flat material-icons gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Close navigation" aria-label="Close navigation"> </button> <div class="devsite-product-name-wrapper"> <a href="/" class="devsite-site-logo-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Site logo" track-type="globalNav" track-name="webDev" track-metadata-position="nav" track-metadata-eventDetail="nav"> <picture> <source srcset="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup-dark-theme.svg" media="(prefers-color-scheme: dark)" class="devsite-dark-theme" alt="web.dev"> <img src="https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg" class="devsite-site-logo" alt="web.dev"> </picture> </a> <span class="devsite-product-name"> <ul class="devsite-breadcrumb-list" > <li class="devsite-breadcrumb-item "> </li> </ul> </span> </div> </div> <div class="devsite-book-nav-wrapper"> <div class="devsite-mobile-nav-top"> <ul class="devsite-nav-list"> <li class="devsite-nav-item"> <a href="/about" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: About" track-name="about" data-category="Site-Wide Custom Events" data-label="Responsive Tab: About" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > About </span> </a> </li> <li class="devsite-nav-item"> <a href="/html" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" data-category="Site-Wide Custom Events" data-label="Responsive Tab: HTML" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > HTML </span> </a> </li> <li class="devsite-nav-item"> <a href="/css" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" data-category="Site-Wide Custom Events" data-label="Responsive Tab: CSS" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > CSS </span> </a> </li> <li class="devsite-nav-item"> <a href="/javascript" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" data-category="Site-Wide Custom Events" data-label="Responsive Tab: JavaScript" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > JavaScript </span> </a> </li> <li class="devsite-nav-item"> <a href="/blog" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Blog" track-name="blog" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Blog" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Blog </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn" class="devsite-nav-title gc-analytics-event devsite-nav-active" data-category="Site-Wide Custom Events" data-label="Tab: Learn" track-name="learn" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Learn" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Learn </span> </a> <ul class="devsite-nav-responsive-tabs"> <li class="devsite-nav-item"> <a href="/learn/privacy" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Privacy" track-name="privacy" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Privacy" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Privacy </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/accessibility" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Accessibility" track-name="accessibility" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Accessibility" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Accessibility </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/html" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: HTML" track-name="html" data-category="Site-Wide Custom Events" data-label="Responsive Tab: HTML" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > HTML </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/images" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Images" track-name="images" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Images" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Images </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/design" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Responsive Design" track-name="responsive design" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Responsive Design" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Responsive Design </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/forms" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Forms" track-name="forms" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Forms" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Forms </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/pwa" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: PWA" track-name="pwa" data-category="Site-Wide Custom Events" data-label="Responsive Tab: PWA" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > PWA </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/css" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: CSS" track-name="css" data-category="Site-Wide Custom Events" data-label="Responsive Tab: CSS" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > CSS </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/performance" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: Performance" track-name="performance" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Performance" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Performance </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/testing" class="devsite-nav-title gc-analytics-event devsite-nav-has-children devsite-nav-active" data-category="Site-Wide Custom Events" data-label="Tab: Testing" track-name="testing" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Testing" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip menu="_book"> Testing </span> <span class="devsite-nav-icon material-icons" data-icon="forward" menu="_book"> </span> </a> </li> <li class="devsite-nav-item"> <a href="/learn/javascript" class="devsite-nav-title gc-analytics-event devsite-nav-has-children " data-category="Site-Wide Custom Events" data-label="Tab: JavaScript" track-name="javascript" data-category="Site-Wide Custom Events" data-label="Responsive Tab: JavaScript" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > JavaScript </span> <span class="devsite-nav-icon material-icons" data-icon="forward" > </span> </a> </li> </ul> </li> <li class="devsite-nav-item"> <a href="/explore" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Explore" track-name="explore" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Explore" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Explore </span> </a> </li> <li class="devsite-nav-item"> <a href="/patterns" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Patterns" track-name="patterns" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Patterns" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Patterns </span> </a> </li> <li class="devsite-nav-item"> <a href="/case-studies" class="devsite-nav-title gc-analytics-event " data-category="Site-Wide Custom Events" data-label="Tab: Case studies" track-name="case studies" data-category="Site-Wide Custom Events" data-label="Responsive Tab: Case studies" track-type="globalNav" track-metadata-eventDetail="globalMenu" track-metadata-position="nav"> <span class="devsite-nav-text" tooltip > Case studies </span> </a> </li> </ul> </div> <div class="devsite-mobile-nav-bottom"> <ul class="devsite-nav-list" menu="_book"> <li class="devsite-nav-item"><a href="/learn/testing/welcome" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/welcome" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/welcome" ><span class="devsite-nav-text" tooltip>Welcome to Learn Testing!</span></a></li> <li class="devsite-nav-item devsite-nav-expandable"><div class="devsite-expandable-nav"> <a class="devsite-nav-toggle" aria-hidden="true"></a><div class="devsite-nav-title devsite-nav-title-no-path" tabindex="0" role="button"> <span class="devsite-nav-text" tooltip>Get Started with Testing</span> </div><ul class="devsite-nav-section"><li class="devsite-nav-item"><a href="/learn/testing/get-started/what-testing-is" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/what-testing-is" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/what-testing-is" ><span class="devsite-nav-text" tooltip>What testing is</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/where-tests-run" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/where-tests-run" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/where-tests-run" ><span class="devsite-nav-text" tooltip>Where tests run</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/testing-environment" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/testing-environment" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/testing-environment" ><span class="devsite-nav-text" tooltip>The testing environment</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/test-types" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/test-types" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/test-types" ><span class="devsite-nav-text" tooltip>Types of automated testing</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/what-to-test" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/what-to-test" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/what-to-test" ><span class="devsite-nav-text" tooltip>What to test and your approach</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/component-testing" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/component-testing" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/component-testing" ><span class="devsite-nav-text" tooltip>Component testing in practice</span></a></li><li class="devsite-nav-item"><a href="/learn/testing/get-started/static-analysis" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/get-started/static-analysis" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/get-started/static-analysis" ><span class="devsite-nav-text" tooltip>Static analysis</span></a></li></ul></div></li> <li class="devsite-nav-item devsite-nav-expandable"><div class="devsite-expandable-nav"> <a class="devsite-nav-toggle" aria-hidden="true"></a><div class="devsite-nav-title devsite-nav-title-no-path" tabindex="0" role="button"> <span class="devsite-nav-text" tooltip>Assertions and other primitives</span> </div><ul class="devsite-nav-section"><li class="devsite-nav-item"><a href="/learn/testing/assertions/tools" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/assertions/tools" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/assertions/tools" ><span class="devsite-nav-text" tooltip>Tools of the trade</span></a></li></ul></div></li> <li class="devsite-nav-item"><a href="/learn/testing/appendix" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/appendix" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/appendix" ><span class="devsite-nav-text" tooltip>Appendix</span></a></li> <li class="devsite-nav-item"><a href="/learn/testing/coming-soon" class="devsite-nav-title gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Book nav link, pathname: /learn/testing/coming-soon" track-type="bookNav" track-name="click" track-metadata-eventdetail="/learn/testing/coming-soon" ><span class="devsite-nav-text" tooltip>Coming soon</span></a></li> </ul> </div> </div> </nav> </devsite-book-nav> <section id="gc-wrapper"> <main role="main" class="devsite-main-content" has-book-nav has-sidebar > <div class="devsite-sidebar"> <div class="devsite-sidebar-content"> <devsite-toc class="devsite-nav" role="navigation" aria-label="On this page" depth="2" scrollbars ></devsite-toc> <devsite-recommendations-sidebar class="nocontent devsite-nav"> </devsite-recommendations-sidebar> </div> </div> <devsite-content> <article class="devsite-article"> <div class="devsite-article-meta nocontent" role="navigation"> <ul class="devsite-breadcrumb-list" aria-label="Breadcrumb"> <li class="devsite-breadcrumb-item "> <a href="https://web.dev/" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="1" track-type="globalNav" track-name="breadcrumb" track-metadata-position="1" track-metadata-eventdetail="web.dev" > web.dev </a> </li> <li class="devsite-breadcrumb-item "> <div class="devsite-breadcrumb-guillemet material-icons" aria-hidden="true"></div> <a href="https://web.dev/learn" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="2" track-type="globalNav" track-name="breadcrumb" track-metadata-position="2" track-metadata-eventdetail="" > Learn </a> </li> <li class="devsite-breadcrumb-item "> <div class="devsite-breadcrumb-guillemet material-icons" aria-hidden="true"></div> <a href="https://web.dev/learn/testing" class="devsite-breadcrumb-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Breadcrumbs" data-value="3" track-type="globalNav" track-name="breadcrumb" track-metadata-position="3" track-metadata-eventdetail="" > Testing </a> </li> </ul> <devsite-thumb-rating position="header"> </devsite-thumb-rating> </div> <h1 class="devsite-page-title" tabindex="-1"> The testing environment </h1> <devsite-feature-tooltip ack-key="AckCollectionsBookmarkTooltipDismiss" analytics-category="Site-Wide Custom Events" analytics-action-show="Callout Profile displayed" analytics-action-close="Callout Profile dismissed" analytics-label="Create Collection Callout" class="devsite-page-bookmark-tooltip nocontent" dismiss-button="true" id="devsite-collections-dropdown" dismiss-button-text="Dismiss" close-button-text="Got it"> <devsite-bookmark></devsite-bookmark> <span slot="popout-heading"> Stay organized with collections </span> <span slot="popout-contents"> Save and categorize content based on your preferences. </span> </devsite-feature-tooltip> <div class="devsite-page-title-meta"><devsite-view-release-notes></devsite-view-release-notes></div> <devsite-toc class="devsite-nav" depth="2" devsite-toc-embedded > </devsite-toc> <div class="devsite-article-body clearfix "> <p>As introduced in <a href="/learn/testing/get-started/what-testing-is">What testing is</a>, tests in JavaScript are fundamentally just code that we confirm runs successfully, that is, without throwing an <code translate="no" dir="ltr">Error</code>. However, one of the ways this definition is an oversimplification is that it doesn't consider <em>where</em> we run the code, its <em>testing environment</em>.</p> <p>The testing environment can broadly be thought of as two components: the runtime environment you use to run your test (such as Node, or the browser) as well as the APIs available to you.</p> <h2 id="runtime" data-text="The runtime environment" tabindex="-1">The runtime environment</h2> <p>Runtimes like Node, or similar tools like Deno or Bun, are aimed at supporting server-side or general-purpose JS code. Their environments don't include APIs you might expect in a browser, such as creating and working with the DOM and HTML elements, nor any concept of a visual component or render target (that is, not <em>just</em> elements, but rendering those elements visually with CSS to a viewport).</p> <p>As such, these general-purpose runtimes will fail if you try to, for example, render React elements so they can be tested, because there are no <code translate="no" dir="ltr">document</code> or <code translate="no" dir="ltr">window</code> objects available.</p> <aside class="note">In the past, these runtimes also lacked common web-adjacent features like the built-in method <code translate="no" dir="ltr">fetch</code>, or the <code translate="no" dir="ltr">EventTarget</code> class, but more are being added over time. Some features missing in Node include classes such as <code translate="no" dir="ltr">WebSocket</code>, which must be added with a polyfill library.</aside> <p>On the other hand, if you run your tests inside a browser, built-in APIs that you can expect from these runtimes might not be available without polyfilling or some extra work. A common gotcha is something like reading and writing files: it's just not possible to <code translate="no" dir="ltr">import { fs } from 'node:'fs';</code> inside a browser and read a file this way as part of a test.</p> <p>This "web" versus "backend" API problem is a bit out of scope of just testing, because it can be awkward to have a codebase with both server and client parts, but it ties into the idea of writing testable code, which we'll revisit throughout this course.</p> <h2 id="business-logic" data-text="Test algorithmic or business logic" tabindex="-1">Test algorithmic or business logic</h2> <p>Some of your code won't require either Node or browser imports to operate, and therefore, to test. This is something we'll touch on later in this course, but structuring your codebase such that its pure "<a href="https://en.wikipedia.org/wiki/Business_logic">business logic</a>" is separate from rendering or Node-specific code can make it easier to test.</p> <p>For a quick example, you might have a Node function that reads and writes a file from disk, modifying it in the process. By refactoring your function to accept functions that perform the read and write from disk, you've made it testable anywhere.</p> <p>In this case, you can use any environment to test this code, in either a server-side runtime or the browser. In your test, you can provide helpers that store a virtual file in memory or return placeholder data. This kind of helper is fine to introduce in a test, because it's <em>not</em> important to check, for example, that <code translate="no" dir="ltr">fs.writeFileSync</code> works. Focus on your code and what makes it unique or risky.</p> <h2 id="emulate_browser_apis" data-text="Emulate browser APIs" tabindex="-1">Emulate browser APIs</h2> <p>Many testing frameworks, such as Vitest, present you with an option <a href="https://vitest.dev/guide/environment.html">to emulate the browser's API environment</a> without running a browser. Vitest internally uses a library called <a href="https://www.npmjs.com/package/jsdom">JSDOM</a>. This can be a good choice for simple component tests where the overhead of using a browser is high.</p> <p>A common feature of any emulation library is that, although they can emulate a browser—for example, the DOM, elements, and cookies—they don't have a visual component. This means they'll provide an imperative way to work with HTML elements and other primitives, but you can't render the output to an image or a screen, or check an element's position in pixels on the page.</p> <p>Again, this choice can be well-suited for <em>component testing</em>, where a component represents a React element, or a Web Component, or so on. These types of components typically create and interact with the DOM in a relatively small way, and an emulated browser can provide enough functionality to confirm the component works the way you intend. An upcoming section includes an example of a React component test with Vitest and JSDOM.</p> <p>Emulating a browser is a well-established practice—JSDOM was released in 2014—but it will always differ from using a real browser. These differences can be obvious: for example, JSDOM doesn't include a layout engine, so there's no way to check the size of an element or test a complex gesture such as a swipe. The differences can also be <em>subtle</em> and unknown, which is why it's best to keep your JSDOM-based tests concise, so you can 'timebox' the risk that any behavior deviates from the real thing.</p> <h2 id="real-browser" data-text="Control a real browser" tabindex="-1">Control a real browser</h2> <p>To test your code as your users will experience it, using a real browser is the best choice. In practice, testing runtimes that support the browser will start and control instances of a real browser, even if they run 'start' inside Node.js.</p> <p>Controlling a browser as part of a test means it will open just like it would for a user, allowing your test to control it by loading URLs, custom HTML and JS, or whatever is needed to perform your test. You can then write code to act as a user, such as by controlling the mouse, or typing input into input boxes.</p> <aside class="note"> When you're first writing these kinds of tests, the browser will be displayed as you run your code. This is useful for debugging, and for seeing how whatever's under test (such as a component or end-to-end experience) is performing. As you advance and automate these tests, you can run a browser in <em>headless</em> mode, which hides the UI.</aside> <p>Modern tools like <a href="https://webdriver.io/">WebdriverIO</a> or <a href="https://modern-web.dev/docs/test-runner/overview/">Web Test Runner</a> can control all major browsers, and even run multiple instances at the same time. These browsers can run adjacent to the test runner (for example, on your own computer, or as part of a CI action), or be outsourced to external commercial services that will run them for you.</p> <p>More established testing libraries (including Vitest and Jest) often have a browser mode, but because their origin is from Node.js, their browser modes are often "bolted on" and missing useful features. For example, Vitest can't mock module imports in the browser, which is a powerful primitive we use in the example on the next page.</p> <h2 id="in-practice" data-text="In practice" tabindex="-1">In practice</h2> <p>As your tests grow in complexity, it becomes more and more important to use a real browser.</p> <ul> <li>For tests that use no or minimal features from the DOM, even features that are available in Node.js and similar runtimes, like <code translate="no" dir="ltr">fetch</code> or <code translate="no" dir="ltr">EventTarget</code>, the environment doesn't matter.</li> <li>For small component tests, JSDOM can be suitable.</li> <li>Larger tests—for example, <em>end-to-end tests</em>, which can simulate a user logging in and performing a core action—make sense to run completely in a real browser.</li> </ul> <p>This section is heavy on theory and presents different viewpoints on where to run your tests. In practice, your codebase will often use many different approaches to different types of tests based on your needs and what the testing tools provide.</p> <h2 id="check_your_understanding" class="hide-from-toc" data-text="Check your understanding" tabindex="-1">Check your understanding</h2> <div class="wd-assessment"> <devsite-multiple-choice> <div><p>What features of the browser does the emulation layer jsdom *not* support?</p></div> <div correct> <div>The layout engine.</div> <div>Because JSDOM isn't a visual tool, it can't be used to check an element's position on the page, its resolved CSS attributes, or any other parts of a website's layout.</div> </div> <div> <div>WebSocket</div> <div>JSDOM includes the WebSocket polyfill, so code that uses it will work.</div> </div> <div> <div><code translate="no" dir="ltr">requestAnimationFrame</code></div> <div>With the `pretendToBeVisual` flag, jsdom will invoke the 'animation' callback at 60fps, even though nothing is actually drawn.</div> </div> </devsite-multiple-choice> </div> </div> <devsite-thumb-rating position="footer"> </devsite-thumb-rating> <div class="devsite-floating-action-buttons"> </div> </article> <devsite-content-footer class="nocontent"> <p>Except as otherwise noted, the content of this page is licensed under the <a href="https://creativecommons.org/licenses/by/4.0/">Creative Commons Attribution 4.0 License</a>, and code samples are licensed under the <a href="https://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a>. For details, see the <a href="https://developers.google.com/site-policies">Google Developers Site Policies</a>. Java is a registered trademark of Oracle and/or its affiliates.</p> <p>Last updated 2024-01-31 UTC.</p> </devsite-content-footer> <devsite-notification > </devsite-notification> <div class="devsite-content-data"> <template class="devsite-content-data-template"> [[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2024-01-31 UTC."],[],[]] </template> </div> </devsite-content> </main> <devsite-footer-promos class="devsite-footer"> </devsite-footer-promos> <devsite-footer-linkboxes class="devsite-footer"> <nav class="devsite-footer-linkboxes nocontent" aria-label="Footer links"> <ul class="devsite-footer-linkboxes-list"> <li class="devsite-footer-linkbox wd-footer-promo"> <h3 class="devsite-footer-linkbox-heading no-link">web.dev</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <h3 class="devsite-footer-linkbox-heading no-link"> web.dev </h3> <div class="devsite-footer-linkbox-description">We want to help you build beautiful, accessible, fast, and secure websites that work cross-browser, and for all of your users. This site is our home for content to help you on that journey, written by members of the Chrome team, and external experts.</div> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Contribute</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues/new?component=1400680&template=1857359" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > File a bug </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://issuetracker.google.com/issues?q=status:open%20componentid:1400680&s=created_time:desc" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > See open issues </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Related Content</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://developer.chrome.com/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > Chrome for Developers </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://blog.chromium.org/" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > Chromium updates </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/case-studies" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Case studies </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/shows" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > Podcasts & shows </a> </li> </ul> </li> <li class="devsite-footer-linkbox "> <h3 class="devsite-footer-linkbox-heading no-link">Follow</h3> <ul class="devsite-footer-linkbox-list"> <li class="devsite-footer-linkbox-item"> <a href="https://twitter.com/ChromiumDev" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 1)" > @ChromiumDev on X </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.youtube.com/user/ChromeDevelopers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 2)" > YouTube </a> </li> <li class="devsite-footer-linkbox-item"> <a href="https://www.linkedin.com/showcase/chrome-for-developers" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 3)" > Chrome for Developers on LinkedIn </a> </li> <li class="devsite-footer-linkbox-item"> <a href="/static/blog/feed.xml" class="devsite-footer-linkbox-link gc-analytics-event" data-category="Site-Wide Custom Events" data-label="Footer Link (index 4)" > RSS </a> </li> </ul> </li> </ul> </nav> </devsite-footer-linkboxes> <devsite-footer-utility class="devsite-footer"> <div class="devsite-footer-utility nocontent"> <nav class="devsite-footer-utility-links" aria-label="Utility links"> <ul class="devsite-footer-utility-list"> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/terms" data-category="Site-Wide Custom Events" data-label="Footer Terms link" > Terms </a> </li> <li class="devsite-footer-utility-item "> <a class="devsite-footer-utility-link gc-analytics-event" href="//policies.google.com/privacy" data-category="Site-Wide Custom Events" data-label="Footer Privacy link" > Privacy </a> </li> <li class="devsite-footer-utility-item glue-cookie-notification-bar-control"> <a class="devsite-footer-utility-link gc-analytics-event" href="#" data-category="Site-Wide Custom Events" data-label="Footer Manage cookies link" aria-hidden="true" > Manage cookies </a> </li> </ul> <devsite-language-selector> <ul role="presentation"> <li role="presentation"> <a role="menuitem" lang="en" >English</a> </li> <li role="presentation"> <a role="menuitem" lang="de" >Deutsch</a> </li> <li role="presentation"> <a role="menuitem" lang="es_419" >Español – América Latina</a> </li> <li role="presentation"> <a role="menuitem" lang="fr" >Français</a> </li> <li role="presentation"> <a role="menuitem" lang="id" >Indonesia</a> </li> <li role="presentation"> <a role="menuitem" lang="it" >Italiano</a> </li> <li role="presentation"> <a role="menuitem" lang="pl" >Polski</a> </li> <li role="presentation"> <a role="menuitem" lang="pt_br" >Português – Brasil</a> </li> <li role="presentation"> <a role="menuitem" lang="vi" >Tiếng Việt</a> </li> <li role="presentation"> <a role="menuitem" lang="tr" >Türkçe</a> </li> <li role="presentation"> <a role="menuitem" lang="ru" >Русский</a> </li> <li role="presentation"> <a role="menuitem" lang="he" >עברית</a> </li> <li role="presentation"> <a role="menuitem" lang="ar" >العربيّة</a> </li> <li role="presentation"> <a role="menuitem" lang="fa" >فارسی</a> </li> <li role="presentation"> <a role="menuitem" lang="hi" >हिंदी</a> </li> <li role="presentation"> <a role="menuitem" lang="bn" >বাংলা</a> </li> <li role="presentation"> <a role="menuitem" lang="th" >ภาษาไทย</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_cn" >中文 – 简体</a> </li> <li role="presentation"> <a role="menuitem" lang="zh_tw" >中文 – 繁體</a> </li> <li role="presentation"> <a role="menuitem" lang="ja" >日本語</a> </li> <li role="presentation"> <a role="menuitem" lang="ko" >한국어</a> </li> </ul> </devsite-language-selector> </nav> </div> </devsite-footer-utility> <devsite-panel></devsite-panel> </section></section> <devsite-sitemask></devsite-sitemask> <devsite-snackbar></devsite-snackbar> <devsite-tooltip ></devsite-tooltip> <devsite-heading-link></devsite-heading-link> <devsite-analytics> <script type="application/json" analytics>[]</script> <script type="application/json" tag-management>{"at": "True", "ga4": [], "ga4p": [], "gtm": [{"id": "GTM-MZWCJPP", "purpose": 0}], "parameters": {"internalUser": "False", "language": {"machineTranslated": "False", "requested": "en", "served": "en"}, "pageType": "course", "projectName": "web.dev", "signedIn": "False", "tenant": "web", "recommendations": {"sourcePage": "", "sourceType": 0, "sourceRank": 0, "sourceIdenticalDescriptions": 0, "sourceTitleWords": 0, "sourceDescriptionWords": 0, "experiment": ""}, "experiment": {"ids": ""}}}</script> </devsite-analytics> <devsite-badger></devsite-badger> <script nonce="YAQFCOyPbMDXJ0uWnRHeSZz03atFJQ"> (function(d,e,v,s,i,t,E){d['GoogleDevelopersObject']=i; t=e.createElement(v);t.async=1;t.src=s;E=e.getElementsByTagName(v)[0]; E.parentNode.insertBefore(t,E);})(window, document, 'script', 'https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/js/app_loader.js', '[27,"en",null,"/js/devsite_app_module.js","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web","https://web-dot-devsite-v2-prod-3p.appspot.com",1,null,["/_pwa/web/manifest.json","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/images/video-placeholder.svg","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/favicon.png","https://www.gstatic.com/devrel-devsite/prod/v870e399c64f7c43c99a3043db4b3a74327bb93d0914e84a0c3dba90bbfd67625/web/images/lockup.svg","https://fonts.googleapis.com/css?family=Google+Sans:400,500|Roboto:400,400italic,500,500italic,700,700italic|Roboto+Mono:400,500,700&display=swap"],1,null,[1,6,8,12,14,17,21,25,50,52,63,70,75,76,80,87,91,92,93,97,98,100,101,102,103,104,105,107,108,109,110,112,113,116,117,118,120,122,124,125,126,127,129,130,131,132,133,134,135,136,138,140,141,147,148,149,151,152,156,157,158,159,161,163,164,168,169,170,179,180,182,183,186,191,193,196],"AIzaSyCNm9YxQumEXwGJgTDjxoxXK6m1F-9720Q","AIzaSyCc76DZePGtoyUjqKrLdsMGk_ry7sljLbY","web.dev","AIzaSyB9bqgQ2t11WJsOX8qNsCQ6U-w91mmqF-I","AIzaSyAdYnStPdzjcJJtQ0mvIaeaMKj7_t6J_Fg",null,null,null,["Search__enable_suggestions_from_borg","Profiles__enable_awarding_url","Concierge__enable_pushui","MiscFeatureFlags__enable_explain_this_code","Cloud__enable_cloud_shell_fte_user_flow","Profiles__enable_dashboard_curated_recommendations","EngEduTelemetry__enable_engedu_telemetry","MiscFeatureFlags__developers_footer_dark_image","CloudShell__cloud_code_overflow_menu","Cloud__enable_legacy_calculator_redirect","Search__enable_page_map","Profiles__enable_public_developer_profiles","Experiments__reqs_query_experiments","Profiles__enable_release_notes_notifications","Search__enable_dynamic_content_confidential_banner","MiscFeatureFlags__enable_firebase_utm","MiscFeatureFlags__enable_view_transitions","Cloud__enable_free_trial_server_call","Analytics__enable_clearcut_logging","OnSwitch__enable","Profiles__enable_completecodelab_endpoint","MiscFeatureFlags__enable_project_variables","TpcFeatures__enable_mirror_tenant_redirects","MiscFeatureFlags__emergency_css","Cloud__enable_cloud_dlp_service","Profiles__enable_developer_profiles_callout","DevPro__enable_cloud_innovators_plus","DevPro__enable_developer_subscriptions","BookNav__enable_tenant_cache_key","MiscFeatureFlags__developers_footer_image","Profiles__enable_complete_playlist_endpoint","Cloud__enable_llm_concierge_chat","CloudShell__cloud_shell_button","TpcFeatures__enable_required_headers","Search__enable_ai_eligibility_checks","Profiles__enable_recognition_badges","Cloud__enable_cloud_facet_chat","MiscFeatureFlags__enable_variable_operator","Cloud__enable_cloudx_experiment_ids","Cloud__enable_cloudx_ping","Profiles__enable_profile_collections","Cloud__enable_cloud_shell","Profiles__enable_page_saving","Profiles__require_profile_eligibility_for_signin"],null,null,"AIzaSyA58TaKli1DculwmAmbpzLVGuWc8eCQgQc","https://developerscontentserving-pa.googleapis.com","AIzaSyDWBU60w0P9hEkr29kkksYs8Z7gvZ8u_wc","https://developerscontentsearch-pa.googleapis.com",2,4,null,"https://developerprofiles-pa.googleapis.com",[27,"web","web.dev","web.dev",null,"web-dot-devsite-v2-prod-3p.appspot.com",null,null,[null,null,null,null,null,null,null,null,null,null,null,[1],null,null,null,null,null,null,[1],null,null,null,null,[1,null,1],[1,1,null,1,1]],null,[38,null,null,null,null,null,"/images/lockup.svg","/images/touchicon-180.png",null,null,null,1,1,null,null,null,null,null,null,null,null,2,null,null,null,"/images/lockup-dark-theme.svg",[]],[],null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,[[],[1,1]],[[null,null,null,null,null,["GTM-MZWCJPP"],null,null,null,null,null,[["GTM-MZWCJPP",1]],1]],null,4]]') </script> <devsite-a11y-announce></devsite-a11y-announce> </body> </html>