The Testing Academy · Class Notes Wednesday, 11 November (IST)
Live class · study guide

JavaScript import and export, classes and Playwright setup

Import and export let one file use another file's variables and functions: named exports in curly braces, one default export without them, renaming with as, and import star. Then classes and objects, object versus object reference, the rendering engines Playwright drives, and a first project made with npm init playwright. Files 172 to 175 and the 24_Playwright folder.

By Pramod Dutta, The Testing Academy. Study notes from the Playwright 4x class of Wednesday, 11 November, built from the session recording, the batch repository, which received files 172 to 175, the 24_Playwright project, three cheat-sheet images and two PDFs during the last minutes of the class (commit "feat: add export/import, classes and Playwright"), and the Eraser notes deck, whose section for this class holds the logger code, the CAB and OIC lines, the Playwright features list and the install command. Every .js file on this page was run with Node 22, and the output shown is what it prints, errors included; Node's module warning is shown once, without its process number, and local paths are made relative to the repo. File 173 as pushed stops with ERR_MODULE_NOT_FOUND. The corrected copy of file 173, the changed and added lines shown with their outputs, and the short snippets were written for this page, then run. For these notes, npm init playwright was run in a new folder with the class's four answers: it wrote the same five files the repo holds, byte for byte, and the two example tests then passed in Chromium, Firefox and WebKit. Nothing on this page signs in anywhere.

Hides every output until you tap Reveal, so you can predict it first.
01

What this class covered

  • Import and export: borrowing from another file
  • Renaming with as, and a file name slip
  • Default and named exports
  • Works or error, and import star
  • Playwright's import line, and import versus require
  • Why OOPS
  • A class is the mould, an object the glass
  • class Person in code
  • Object versus object reference
  • What Playwright is
  • Rendering engines and real browsers
  • Installing Playwright
  • Running the example tests
  • What Playwright cannot automate
  • Tasks: set up Playwright before next week
  • Only what a file exports can be imported: fname in testutils.js has no export, so asking for it is a SyntaxError.
  • Named exports come in curly braces, by their exact, case-sensitive name, and as gives one a new name. A file has one default export at most, and it comes in without braces, under any name.
  • The path must match the file exactly: file 173 asks for ./testutil.js, so it stops with ERR_MODULE_NOT_FOUND before it prints anything.
  • In this course, always import: require is the old way.
  • A class is a blueprint with attributes and behaviour (CAB); an object is a real instance of it, in memory (OIC).
  • In const pramod = new Person();, new Person() is the object and pramod is the object reference.
  • npm init playwright sets up a project with a config and an example test, and installs the browsers. Playwright can emulate a phone, but it cannot automate a native mobile app or a desktop app.
02

Import and export: borrowing from another file

This class opened chapter 22 in the repo; the latest notes are the 4 November notes, and the files of the 6 November class, 162 to 171 on async and await, are in the repo's 21_Async_Await folder.

EXPORT IN ONE FILE, IMPORT IN ANOTHER: ONLY WHAT IS EXPORTED CAN BE BORROWED THE FILE THAT SHARES testutils.js export let BASE_URL export function formatUpperCaseString let fname line 7 has no export: it stays inside THE FILE THAT BORROWS 172_IE.js import { BASE_URL, formatUpperCaseString} from './testutils.js'; line 1, then lines 3 to 6 use both import What file 172 prints https://app.vwo.com BASE_URL, on line 3 PRAMOD formatUpperCaseString("Pramod"), on lines 5 and 6 Ask for fname on line 1 as well, and it stops before printing anything: SyntaxError: The requested module './testutils.js' does not provide an export named 'fname'
Files 172 and testutils.js with their real output: export decides what another file may import.

Import and export, the instructor said, means using a variable or a function from one JS file in another JS file. In school, we used to borrow pens, pencils and rubbers from each other; similarly, you can borrow variables and functions in JavaScript.

Two panels. Left, titled Lend it, with the sublabel export: an open pencil case on a school desk, its pen and pencil tagged for lending, a ruler and an eraser untagged. Right, titled Borrow it, with the sublabel import: the tagged pen and pencil on a second desk beside an open notebook, with an arrow coming in.
The class's picture of import and export: a file lends what it marks with export, and another file borrows it.

testutils.js has a variable, BASE_URL, and a function that returns whatever string it is given in uppercase. Write export in front of them, the instructor said, and you can import both in another file:

testutils.jsJavaScript
export let BASE_URL = "https://app.vwo.com";

export function formatUpperCaseString(sname){
    return sname.toUpperCase();
}

let fname = "Pramod";

On its own, testutils.js prints nothing: it only shares. File 172 uses the keyword import to take the two exported names from it:

172_IE.jsJavaScript
import { BASE_URL, formatUpperCaseString} from './testutils.js';

console.log(BASE_URL);

let result = formatUpperCaseString("Pramod");
console.log(result);
Output
https://app.vwo.com
PRAMOD

There are the outputs, the URL as well as the uppercase, as the class saw them. Node also prints a warning after these two lines; it is harmless, as the repo's README says, and the section on import versus require below explains it.

Wherever export is written, we can import it and use it, the instructor said, and if the export is not written, you will not be able to import. Line 7 of testutils.js, fname, has no export. Ask file 172 for it as well:

JavaScript
import { BASE_URL, formatUpperCaseString, fname } from './testutils.js';
Output
22_Export_Import/172_IE.js:1
import { BASE_URL, formatUpperCaseString, fname } from './testutils.js';
                                          ^^^^^
SyntaxError: The requested module './testutils.js' does not provide an export named 'fname'

The file stops on line 1, before anything prints. In the future you can export a class too, the instructor said, once the OOPS concepts are done; utils.js already sketches one in its comments, as the README notes.

03

Renaming with as, and a file name slip

utils.js has functions that format a test case's name, and a BASE_URL of its own:

utils.jsJavaScript
export let BASE_URL = "https://api.example.com";

export function formatTestName(name) {
    return "TC_" + name.toUpperCase();
}

export function formatTestName2(name) {
    return "TC_" + name.toUpperCase();
}

// export class LoginPage {
//   constructor(page) { this.page = page; }
//   async login(u, p) { /* ... */ }
//   async openBrowser(u, p) { /* ... */ }
//   async closerBrowser(u, p) { /* ... */ }
//    async clickElement(u, p) { /* ... */ }
// }

File 173 imports from both. You can use whatever you want, the instructor said, and use a short form too. Both files export a BASE_URL, so as gives each one a local name, and both can be used in one file, as the repo's README explains:

173.jsJavaScript
import { BASE_URL as bul_util, formatTestName } from "./utils.js";
import { BASE_URL as bul_testtul } from "./testutil.js";

console.log(bul_util);
console.log(bul_testtul);
console.log(formatTestName("login"));
Output
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '22_Export_Import/testutil.js' imported from 22_Export_Import/173.js

Line 2 names a file that does not exist. It imports from ./testutil.js, but the file is testutils.js, with an s. Node resolves every import before it runs any line, so file 173 prints nothing at all, not even the URL from utils.js, whose import on line 1 is fine. The repo's README points this out too.

With the name corrected, in a copy kept outside the repo, file 173 prints all three:

JavaScript
import { BASE_URL as bul_util, formatTestName } from "./utils.js";
import { BASE_URL as bul_testtul } from "./testutils.js";

console.log(bul_util);
console.log(bul_testtul);
console.log(formatTestName("login"));
Output
https://api.example.com
https://app.vwo.com
TC_LOGIN
Name in file 173 Comes from Prints
bul_util BASE_URL in utils.js https://api.example.com
bul_testtul BASE_URL in testutils.js https://app.vwo.com
formatTestName("login") utils.js TC_LOGIN
04

Default and named exports

A cheat sheet titled Default vs Named Exports. A box labelled logs/logger.js holds a gift marked default and two name tags, logBetter and logBetter2. An arrow from the gift leads to import log from './logs/logger.js';, noted no { }, any name. An arrow from the logBetter tag leads to import { logBetter } from './logs/logger.js';, noted { } and the exact name. A banner at the bottom reads: One default per file, many named.
From the batch repo's README: the default comes in without braces, under any name, and a named export comes in braces, by its exact name.

Can we import from another folder too, a learner asked. Yes, the instructor said: we can import from anywhere. The example was a folder named logs, and a file in it, logger.js, with one thing called a default export:

logs/logger.jsJavaScript
// Default Export -> Export One Main Thing

export default function log(message) {
    console.log("[LOG] " + message);
}


export function logBetter(message) {
    console.log("-----------");
    console.log("[LOGS] " + message);
    console.log("-----------");
}

export function logBetter2(message) {
    console.log("-----------");
    console.log("[LOGS] " + message);
    console.log("-----------");
}

The commit's message adds that the repo's root .gitignore ignores every logs/ folder, so this file had to be force-added, because file 174 imports it. File 174 takes the default and two named exports:

174_Logs.jsJavaScript
import log from './logs/logger.js';
import { logBetter, logBetter2 } from './logs/logger.js';

log('Starting');


logBetter("Starting the TC of the app.vwo.com");
Output
[LOG] Starting
-----------
[LOGS] Starting the TC of the app.vwo.com
-----------

If you use the default keyword, you do not have to use curly braces, the instructor said: that is line 1. With a non-default, you have to import it with the curly braces: line 2, the named exports. logBetter2 is imported but never called, so only one dashed block prints.

Can we import an asynchronous function, a learner asked. Absolutely, the instructor said: there is no problem with asynchronous or synchronous, and you can import any function.

Is there any advantage of using a default, a learner asked. The instructor compared it with Java's main function: if a file has several functions and you want at least one of them imported, make that one the default, the way a Java program has at least one main function; the rest you import with braces. The default still needs its own import line, import log from ..., only without braces. And it will be only one default, the instructor said: generally, multiple defaults will not be there.

It is like importing a package, a learner said. Yes, the instructor said, and asked whether anyone from a Java background remembered doing the same in Java.

05

Works or error, and import star

During the class, the instructor made a learning document on default versus named exports, and put it in the repo's learning folder as a PDF, default_vs_named_export_eli5.pdf, only for your notes. Its rules, as read out in class: a default is one per file, with no curly braces, and you can call it any name you like; named exports use the curly braces, and the exact name must match. The PDF adds that a named export can be renamed with as. Parts of its "Works or error?" list were read out too; here each line is run against the class's logs/logger.js:

Line Node The PDF's note
import log from './logs/logger.js'; Works
import anything from './logs/logger.js'; Works The default can have any name
import { logBetter } from './logs/logger.js'; Works
import { logbetter } from './logs/logger.js'; SyntaxError: does not provide an export named 'logbetter' Names are case-sensitive
import { log } from './logs/logger.js'; SyntaxError: does not provide an export named 'log' log is the default, so no braces
export default 1; export default 2; SyntaxError: Identifier '.default' has already been declared Only one default per file

The "works" lines were each run with a call after them, which printed as expected. Both kinds can come in one line, the PDF adds: default first, then the named ones in braces. File 174 with its first two lines joined prints the same:

JavaScript
import log, { logBetter, logBetter2 } from './logs/logger.js';
Output
[LOG] Starting
-----------
[LOGS] Starting the TC of the app.vwo.com
-----------

Can we import all the functions of a particular file, a learner asked. We can, the instructor said, by using import star; there are rules to follow, though, and not everything can be done. In a snippet written for these notes, import * as collects every export of logger.js in one object, with the default under the name default:

JavaScript
import * as logger from './logs/logger.js';

logger.logBetter2("Every export, in one object");
console.log(Object.keys(logger));
Output
-----------
[LOGS] Every export, in one object
-----------
[ 'default', 'logBetter', 'logBetter2' ]
06

Playwright's import line, and import versus require

Every Playwright test file starts with an import. This is the first line of the class's example test:

tests/example.spec.tsTypeScript
import { test, expect } from '@playwright/test';

Is it a default import or a non-default import, the instructor asked. A non-default import, the instructor said, and a multiple import: two names in one line. What is the @? It means the import is in a package, the instructor said, a proper library folder, and the package is a separate discussion. In the class's Playwright project, it is the folder node_modules/@playwright/test. How many imports come in Playwright, a learner asked. That is not the right question, the instructor said: it depends on how many you want to use.

The ELI5 PDF adds that Playwright also has a default export, and it is test again. Checked in the class's Playwright project:

JavaScript
import test, { test as namedTest } from '@playwright/test';

console.log(test === namedTest);
Output
true

Import or require? People have this confusion too, the instructor said: sometimes people use require, and sometimes import. Please remember, we are working with TypeScript: we will always use import. Require is in Node.js; it is a JavaScript concept, but we will not use it, and we will avoid it. Importing is the new way, and require is the old way. The repo has the old way in chapter 8: the prompt-sync lesson in the 14 October notes starts with const prompt = require("prompt-sync")();.

That is where the warning after file 172's output comes from. The repo's package.json does not say which kind of module its .js files are, so Node first reads file 172 the old way, as CommonJS, finds import, and reads it again as an ES module. Without its process number, the warning reads:

Text
[MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of 22_Export_Import/172_IE.js is not specified and it doesn't parse as CommonJS.
Reparsing as ES module because module syntax was detected. This incurs a performance overhead.
To eliminate this warning, add "type": "module" to package.json.

The warning is harmless, the README says, and adding "type": "module" to package.json would break the require lessons of chapter 8. In a copy of the repo with that line added, chapter 8's 62_FS_readFile.js stops at its require:

Terminal
node 08_chapter_UserInput/62_FS_readFile.js
Output
Enter the number!
08_chapter_UserInput/62_FS_readFile.js:2
const data = require('fs').readFileSync(0, 'utf8');
             ^

ReferenceError: require is not defined in ES module scope, you can use import instead
07

Why OOPS

Till here, the class had all the JavaScript concepts that are good to know, the instructor said; now comes the interesting part, the OOPS concept, and how to use it with TypeScript. It is the last important concept of TypeScript before jumping into Playwright, in the instructor's words.

Not theory, more practical, the instructor said, but a little theory first: why was OOPS introduced? Before it, there was procedural programming, where everything was a function: function one, function two and function three, calling each other. That is not a real way of solving a problem, because we live in real life: we are not functions, we are classes and objects.

Earlier, the class discussed objects in JavaScript (the 30 October notes): those were just key-value pairs. The class and object of OOPS are totally different, the instructor stressed: do not get confused.

This concept will be useful for you mostly in Playwright, the instructor said, because in the Playwright framework we will be using all the concepts of OOPS: classes and objects, encapsulation, polymorphism, inheritance, abstraction and the constructor. Interface is a part of abstraction, as well as the abstract class. They are the same concepts of Java you have probably heard about. So if somebody asks whether you have used the OOPS concept in your Playwright framework, the answer will always be yes.

08

A class is the mould, an object the glass

Two panels. Left, titled The mould, with the sublabel the class: an open, empty two-part steel tumbler mould on a workbench beside a rolled blueprint that shows a tumbler. Right, titled The glasses, with the sublabel the objects: the closed mould with an arrow to two identical steel tumblers, each with its own small tag.
The class's picture of a class: the mould itself holds nothing, and every glass made from it is an object.

A class is like a blueprint, and an object is an instance of a class, the instructor said: a lot of people know the concept, but they are not able to relate it to real life. Everything which is created has a blueprint.

The first example was a steel glass, held up to the camera. How was this glass created? First of all, a mould was created: a design, a blueprint of the mould. Put the stainless steel into the mould, and it becomes a glass. The mould itself has nothing; it is just a structure. The real glass that comes out is called the object.

The second was the blueprint of a building. It has two things, A and B, the instructor said. A is for attributes: its lines, how many bathrooms there are, the colour of the building. B is for behaviour: the building can have a lift, and you can have a party in it. That is a class.

A person is a class too, with attributes such as name, age, gender and occupation, and functionality such as walk, eat, sleep and work. On the iPad, the instructor's example was a dog: breed, size, age and colour are its attributes; eat, sleep, sit and run its behaviour; and real dogs, such as a Maltese and a Chow Chow, are the objects.

Objects are real, the instructor said: they are present in your memory. A class is not real; it is just a blueprint to create an object, and objects are allocated memory space when they are created. Two rules to remember, as the deck writes them: class, a blueprint containing attributes and behaviours (CAB); object, a real entity, an instance of the class (OIC).

09

class Person in code

A cheat sheet titled Class vs Object. A blueprint card, class Person, lists #name, #age, eat() and sleep(). Two arrows labelled new Person() lead to two cards, pramod and amit, each with its own #name and #age. A banner at the bottom reads: # fields are private to the class.
From the batch repo's README: one class, and two objects made from it with new, each with its own fields.

File 175 is a class in code: the keyword class, the name Person, and curly braces.

175_Class.jsJavaScript
class Person{
    // Attributes | Instance variables |  Data members | Properties
    #name;
    #age;
    #rollNo;
    #phoneNo;

    // Behaviour | Methods | Members Functions | 
    eat(){}
    sleep(){}
}


// # and we are not using function key in class for the f(n)

// CAB -> Class has the A, B

const pramod = new Person();
// Object -> new Person();
// Object Ref -> pramod

const amit = new Person();

It prints nothing: it only creates two objects. A class has attributes and behaviour, the instructor said, and people call them by other names too, all of them alternate names. Lines 2 and 8 list them:

The class has Also called In file 175
Attributes instance variables, data members, properties #name, #age, #rollNo, #phoneNo
Behaviour methods, member functions eat(), sleep()

What attributes can a person have? Everyone has a name, the instructor said, and an age, a roll number, a phone number; the behaviour is eat, sleep and so on. Two things to notice, as line 14's comment says: in a class, the functions do not use the function keyword, and the attributes start with #. What # is will be explained with the constructor and encapsulation in the upcoming class; by the way, # basically means private, the instructor said.

Log the two objects, and Node shows two Person objects that are not the same:

JavaScript
console.log(pramod);
console.log(amit);
console.log(pramod === amit);
Output
Person {}
Person {}
false

Node prints Person {} for each: it does not show the private # fields.

10

Object versus object reference

AN OBJECT LIVES IN MEMORY; THE NAME ON THE LEFT IS ONLY A REFERENCE TO IT const pramod = new Person(); object reference the object Line 18 of file 175. Lines 19 and 20 say the same: // Object -> new Person(); // Object Ref -> pramod new creates a new memory; pramod refers to it. Lines 18 and 22, with the values from the class's memory drawing stored in each references objects, each in its own memory pramod Person { name: 'Pramod', age: 69 } amit Person { name: 'Amit', age: 71 } pramod === amit is false: two different objects, with the same core structure.
The interview question, drawn from real runs of file 175: two references, two objects, one structure.

Class Person is just a blueprint, not a real person, the instructor said: the real person is created with the keyword new, as in new Person(). Then came one of the important interview questions: in line 18, which one is the object, and which one is the object reference? If you think pramod is the object, you are wrong, the instructor said: new Person() is the object, and pramod is the object reference. Lines 19 and 20 of the file say the same.

new Person() creates a new memory, and pramod gets a reference to it, the instructor said; a second object gets a different memory. Drawing the memory, the instructor stored values in each: pramod.name is Pramod and amit.name is Amit, pramod.age is 69 and amit.age is 71. Both objects are different, the instructor said, but their core structure is the same. Those values, added to file 175:

JavaScript
pramod.name = "Pramod";
amit.name = "Amit";
pramod.age = 69;
amit.age = 71;

console.log(pramod);
console.log(amit);
Output
Person { name: 'Pramod', age: 69 }
Person { name: 'Amit', age: 71 }

These are new public properties. pramod.name = "Pramod" adds a property called name from outside the class; it does not touch the private #name field, which only code inside the class can reach. How a class fills its own fields comes with the constructor.

The README adds that pramod.#name outside the class is a SyntaxError:

JavaScript
console.log(pramod.#name);
Output
23_Class_Objects/175_Class.js:24
console.log(pramod.#name);
                  ^

SyntaxError: Private field '#name' must be declared in an enclosing class

Person is the mould, and pramod and amit are the glasses, a learner summed up. Absolutely right, the instructor said.

In JavaScript we do not have the Java form, Person pramod = new Person(), the instructor said: that is a Java concept. In JavaScript it is const pramod, a reference. Why const, and can we use let? Absolutely, you can use let, the instructor said: const makes sure that the object reference is stored only one time. Each row below is file 175 with one change, run here:

You write Node
Person pramod = new Person(); in line 18, the Java way SyntaxError: Unexpected identifier 'pramod'
pramod = amit; at the end, with const in line 18 TypeError: Assignment to constant variable.
pramod = amit; at the end, with let in line 18 Works: pramod === amit is now true
11

What Playwright is

While learning the OOPS concept, the class also did the installation of Playwright, in parallel. First, what is Playwright? A tool, for web automation, the instructor said. Its home page says that Playwright "enables reliable web automation for testing, scripting, and AI agents"; the AI agents part is a new line they have added, the instructor pointed out.

The deck's definition: Playwright is a Node.js library which can help you to automate the browser and rendering engine. Its features, which the class went through, are in the deck as:

  • cross-browser testing (Chromium, Firefox, WebKit)
  • auto-wait for elements
  • network interception
  • mobile emulation
  • parallel test execution
  • a built-in test runner
  • a trace viewer for debugging
12

Rendering engines and real browsers

PLAYWRIGHT DRIVES THREE RENDERING ENGINES; EVERYDAY BROWSERS ARE BUILT ON THEM The class's playwright.config.ts: three projects chromium OPEN SOURCE, USED BY GOOGLE Chromium rendering engine Google Chrome Microsoft Edge Opera Brave firefox BY MOZILLA Gecko rendering engine Firefox Tor Browser webkit MOSTLY BY APPLE WebKit rendering engine Safari Real browsers, each built from its engine with its own customisations on top.
The family tree as the class drew it: the config's three projects drive the engines, and the browsers you use every day are built on them.

Why does Playwright say Chromium, Firefox and WebKit? Where is Safari, where is Chrome? To understand this, the instructor explained the rendering engine. Chromium is an open source project, used by Google, and it is a rendering engine: almost 90% of a browser, you can say, and the remaining 10% you can customise according to your needs. Companies took the Chromium project and added the last 10%, their customisations: from Chromium, Microsoft Edge was created, Google Chrome, Opera and the Brave browser. These are the real browsers. The advantage of the engine is that it is very lightweight.

Mozilla has its own rendering engine, Gecko, from which come Firefox and the Tor browser. WebKit is the rendering engine Safari was created from, mostly taken care of by Apple.

Why does Playwright use the engines? If you have 12,000 test cases that you want to run daily, let's suppose, do you need Chrome or Chromium? If you use Chrome, it will be slow, the instructor said, and if you use Chromium, it will be fast. When you add more customisation, your car gets slow, the instructor said: change the tyres or part of the hood, and it becomes slow, especially the mileage. A better example, the instructor added, is our operating system.

Two panels. Left, titled Stock car, with the sublabel rendering engine: a plain hatchback with long speed lines and a gauge reading high. Right, titled Customised car, with the sublabel real browser: the same car with big wheels, a roof box, lights and a spoiler, one short speed line and a gauge reading low.
The instructor's comparison: add customisations to a car and it gets slower, especially in mileage.

Does that mean Playwright does not support the real browsers? It supports them, the instructor said. The class's config has them ready, commented out:

playwright.config.tsTypeScript
    /* Test against branded browsers. */
    // {
    //   name: 'Microsoft Edge',
    //   use: { ...devices['Desktop Edge'], channel: 'msedge' },
    // },
    // {
    //   name: 'Google Chrome',
    //   use: { ...devices['Desktop Chrome'], channel: 'chrome' },
    // },

Playwright's page on browsers says the same: it can run tests on Chromium, WebKit and Firefox, as well as branded browsers such as Google Chrome and Microsoft Edge.

The class's questions about the engines:

  • Edge and Chrome are based on Chromium, so ideally we do not run them separately? Mostly yes, the instructor said, but you can still run them separately, in Edge or Chrome, if your company demands.
  • Is Chromium like a class, and the other browsers its instances? Not exactly, the instructor said: Chromium is also a real entity, so class and object will not work here. It is almost a browser, but not a ready browser, and you can also run Chromium by yourself.
  • Any reason to prefer Firefox? It depends on your company, the instructor said.
13

Installing Playwright

ONE COMMAND, FOUR ANSWERS, AND A READY PROJECT IN A NEW FOLDER npm init playwright In class: folder 24, the repo's 24_Playwright Run it in a terminal opened in that folder. THE CLASS'S FOUR ANSWERS Four questions Do you want to use TypeScript or JavaScript? TypeScript Where to put your end-to-end tests? tests Add a GitHub Actions workflow? no: type n Install Playwright browsers? yes WHAT IT WRITES Five files, as in the repo package.json package-lock.json playwright.config.ts tests/example.spec.ts .gitignore THEN IT DOWNLOADS, ONCE The browsers, for 1.65.0 Chrome for Testing 158.0.8127.18 Chrome Headless Shell 158.0.8127.18 Firefox 159.0 WebKit 27.2 FFmpeg
The class's install, checked by running it again: the same answers wrote the same five files, byte for byte.

How do you install Playwright? It is very simple, the instructor said: you just have to run one command, npm init playwright. In class, that was a new folder, 24 (the repo's 24_Playwright), a terminal opened in it, and four questions: TypeScript, the tests folder, no GitHub Actions for now, and yes to installing the browsers.

The same command was run for these notes, in a new, empty folder, with the class's four answers. What the terminal showed:

Terminal
npm init playwright
Output
Need to install the following packages:
create-playwright@1.17.139
Ok to proceed? (y) y


> npx
> create-playwright

Getting started with writing end-to-end tests with Playwright:
Initializing project in '.'
✔ Do you want to use TypeScript or JavaScript? · TypeScript
✔ Where to put your end-to-end tests? · tests
✔ Add a GitHub Actions workflow? (Y/n) · false
✔ Install Playwright browsers (can be done manually via 'npx playwright install')? (Y/n) · true
Initializing NPM project (npm init -y)…
Wrote to 24_Playwright/package.json:

{
  "name": "24_playwright",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}



Installing Playwright Test (npm install --save-dev @playwright/test)…

added 3 packages, and audited 4 packages in 2s

found 0 vulnerabilities
Installing Types (npm install --save-dev @types/node)…

added 2 packages, and audited 6 packages in 1s

found 0 vulnerabilities
Writing playwright.config.ts.
Writing tests/example.spec.ts.
Writing package.json.
Downloading browsers (npx playwright install)…
✔ Success! Created a Playwright Test project at 24_Playwright

Inside that directory, you can run several commands:

  npx playwright test
    Runs the end-to-end tests.

  npx playwright test --ui
    Starts the interactive UI mode.

  npx playwright test --project=chromium
    Runs the tests only on Desktop Chrome.

  npx playwright test example
    Runs the tests in a specific file.

  npx playwright test --debug
    Runs the tests in debug mode.

  npx playwright codegen
    Auto generate tests with Codegen.

We suggest that you begin by typing:

    npx playwright test

And check out the following files:
  - ./tests/example.spec.ts - Example end-to-end test
  - ./playwright.config.ts - Playwright Test configuration

Visit https://playwright.dev/docs/intro for more information. ✨

Happy hacking! 🎭

Two things differ from a quick run of Enter, Enter, Enter, Enter. npm first asks "Ok to proceed?" when it has not fetched create-playwright before: answer y. And the GitHub Actions question now defaults to yes, (Y/n): for the class's "not right now", type n. A first run here with Enter alone wrote a workflow file as well.

It creates the folder structure for you, the instructor said, with files you can use directly. They are the files the repo holds, and the run above wrote the same five, byte for byte:

Text
24_Playwright/
├── .gitignore
├── package-lock.json
├── package.json
├── playwright.config.ts
└── tests/
    └── example.spec.ts
package.jsonJSON
{
  "name": "24_playwright",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {},
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@playwright/test": "^1.65.0",
    "@types/node": "^26.6.6"
  }
}

In the tests folder, a spec file was already created, with its test cases written in it, the instructor pointed out:

tests/example.spec.tsTypeScript
import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Expect a title "to contain" a substring.
  await expect(page).toHaveTitle(/Playwright/);
});

test('get started link', async ({ page }) => {
  await page.goto('https://playwright.dev/');

  // Click the get started link.
  await page.getByRole('link', { name: 'Get started' }).click();

  // Expects page to have a heading with the name of Installation.
  await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
});

The browsers were already on this machine, so the run above downloaded none. On a first install it downloads them, and it takes time, the instructor said, so get the setup ready early. In class it was downloading Chrome for Testing, and was going to download Firefox and Safari too, all of them, the instructor said. For Playwright 1.65.0, the version in the class's package.json, this lists what the install step fetches (the first line of each entry; their folders and download links are left out):

Terminal
npx playwright install --dry-run
Output
Chrome for Testing 158.0.8127.18 (playwright chromium v1254)
Chrome Headless Shell 158.0.8127.18 (playwright chromium-headless-shell v1254)
Firefox 159.0 (playwright firefox v1572)
WebKit 27.2 (playwright webkit v2388)
FFmpeg (playwright ffmpeg v1013)

The class's other questions about the setup:

  • For any language, is it npm? We will be using npm only, the instructor said.
  • Do we create a new project and run the command inside it? Yes, the instructor said.
  • Will we cover TypeScript too? Yes, the instructor said: wait and watch.
  • npx and npm: npx is used for execution, and npm is for libraries, the instructor said; it will be explained, so do remind the instructor.
14

Running the example tests

A cheat sheet titled What npx playwright test Does. A terminal running npx playwright test leads to playwright.config.ts, showing testDir: './tests' and projects: [...], then to tests/example.spec.ts, which branches to three browsers, chromium, firefox and webkit, all leading to an HTML report. A banner at the bottom reads: First run? npx playwright install.
From the batch repo's README: one command runs every test in every browser project of the config, and writes an HTML report.

The class stopped at the install: we will not go further, the instructor said. The repo's README gives the commands to run the project, and the config says what they run: three browser projects, so the two tests run six times.

playwright.config.tsTypeScript
export default defineConfig({
  testDir: './tests',
  /* Run tests in files in parallel */
  fullyParallel: true,
  /* Fail the build on CI if you accidentally left test.only in the source code. */
  forbidOnly: !!process.env.CI,
  /* Retry on CI only */
  retries: process.env.CI ? 2 : 0,
  /* Opt out of parallel tests on CI. */
  workers: process.env.CI ? 1 : undefined,
  /* Reporter to use. See https://playwright.dev/docs/test-reporters */
  reporter: 'html',
  /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
  use: {
    /* Base URL to use in actions like `await page.goto('')`. */
    // baseURL: 'http://localhost:3000',

    /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
    trace: 'on-first-retry',
  },

  /* Configure projects for major browsers */
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },

    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },

    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },

Run for these notes in the class's project folder, with the list reporter so each test shows on its own line, and one worker so the order stays the same:

Terminal
npx playwright test --reporter=list --workers=1
Output
Running 6 tests using 1 worker

  ✓  1 [chromium] › tests/example.spec.ts:3:1 › has title (916ms)
  ✓  2 [chromium] › tests/example.spec.ts:10:1 › get started link (375ms)
  ✓  3 [firefox] › tests/example.spec.ts:3:1 › has title (973ms)
  ✓  4 [firefox] › tests/example.spec.ts:10:1 › get started link (483ms)
  ✓  5 [webkit] › tests/example.spec.ts:3:1 › has title (760ms)
  ✓  6 [webkit] › tests/example.spec.ts:10:1 › get started link (473ms)

  6 passed (5.9s)

Without those two options, the config's html reporter writes a report instead, and npx playwright show-report opens it, as the README says. The test file uses what the JavaScript chapters built up: imports, arrow functions, and async and await, as the README puts it. Its test body is a callback, as the 4 November notes showed.

15

What Playwright cannot automate

Can we handle mobile app automation using Playwright, a learner asked. Mobile emulation you can do, the instructor said, but proper native mobile application automation you cannot. Desktop automation using Playwright is still not supported either: for that, we have to use certain different libraries. The emulation is in the class's config, commented out:

playwright.config.tsTypeScript
    /* Test against mobile viewports. */
    // {
    //   name: 'Mobile Chrome',
    //   use: { ...devices['Pixel 5'] },
    // },
    // {
    //   name: 'Mobile Safari',
    //   use: { ...devices['iPhone 12'] },
    // },

Playwright's installation page puts it the same way: native mobile emulation for Chrome (Android) and Mobile Safari.

16

Tasks and announcements

  • Today's task: set up Playwright in one of your folders, with npm init playwright. The instructor asked Meeti to add it as a task in the chat. It takes time to download all the browsers, so make sure the setup is ready for the upcoming classes: next week, the instructor wants everyone to be ready.
  • Next class: the difference between Selenium, Cypress and Playwright, and the Playwright architecture, the instructor said. What # means, with the constructor and encapsulation, is to be explained in the upcoming class too.
  • Towards Playwright: the journey starts probably from Friday or so; next Friday, the instructor thinks.
  • Separate Playwright notes: the instructor has a separate set of notes for Playwright, to be given to the batch.
  • Interview practice questions: there are almost 1500, and the instructor wants all of you to start completing them. When you have understood a question, mark it done, and you will earn points.
  • Recordings on your phone: there is an Android app; download it, log in with your own username and password, and you can access the recordings through mobile. It is also available on iOS, the instructor said, adding: "I hope so."
  • Tester Town: it is still in alpha, with only a few students; its 24 by 7 chat, with a game mode, will be enabled for everyone soon, probably this week, if everything works fine.
  • Extra sessions: a lot of people did not attend the GitHub session, which was important, the instructor said: if you missed it, watch the recording. Whenever there is a session, you will be invited, so make sure you attend.
  • The code is pushed: files 172 to 175 and the Playwright project are in the repo's 22_Export_Import, 23_Class_Objects and 24_Playwright folders, and the two PDFs in 00_learning.

Task. Set up Playwright in a project folder with npm init playwright, before next week: TypeScript, the tests folder, no GitHub Actions workflow for now, and yes to the browsers.

Show the answer

Answer. Make a new folder, open a terminal in it and run npm init playwright. Answer y if npm asks to fetch create-playwright, press Enter for TypeScript and for the tests folder, type n for the GitHub Actions workflow, which now defaults to yes, and press Enter to install the browsers. You get the five files of the repo's 24_Playwright: package.json, package-lock.json, playwright.config.ts, tests/example.spec.ts and .gitignore. Then npx playwright test --reporter=list should report 6 passed: two tests in three browsers.

In const pramod = new Person();, which one is the object, and which one is the object reference?

Worth practising too: What is the difference between a default export and a named export? Why does file 173 print nothing at all? What does # in front of a field mean? Why does Playwright use rendering engines such as Chromium, and can it still run Chrome? Can Playwright automate a native mobile app, or a desktop app?

Show the answer

Answer. new Person() is the object, created in memory, and pramod is the object reference that points to it; a second new Person() is a different object, so pramod === amit is false. A file has one default export at most, imported without braces under any name; it can have many named exports, imported in braces by their exact name, or renamed with as. File 173 imports from ./testutil.js, a file that does not exist, and Node resolves every import before it runs any line, so it stops with ERR_MODULE_NOT_FOUND first. # basically means private: pramod.#name outside the class is a SyntaxError. A rendering engine is almost a browser and very lightweight: with 12,000 tests a day, Chrome would be slow and Chromium fast, as the instructor put it. Playwright still supports the real browsers, and the config has Google Chrome and Microsoft Edge ready, commented out. It can emulate a phone, but it cannot automate a native mobile app or a desktop app.