Where this sits, and what changes after it
This was the last of the JavaScript run-up. The stated plan is to finish these fundamentals this week and start Playwright itself immediately after.
Two topics, and they are really one argument. Export and import comes first, it runs into a wall, and classes are the way through the wall.
The decision announced in this session: the batch moves to TypeScript. Not as a new language to learn, but as the same JavaScript with rules added. Everything already covered across twenty chapters stays valid. What changes is the file extension and a handful of keywords, and the framework files will be .ts from here on. TypeScript compiles back to JavaScript anyway, so the engine still runs JavaScript in the end.
Export and import, and why files need them
The setup: one file has things, another file wants them.
// testutil.js
export let BASE_URL = "https://app.vwo.com";
export function formatUpperCaseString(sname) {
return sname.toUpperCase();
}
let fname = "Pramod"; // no export, so importers cannot reach it
// 155.js
import { BASE_URL, formatUpperCaseString } from './testutil.js';
console.log(BASE_URL); // https://app.vwo.com
console.log(formatUpperCaseString("Pramod")); // PRAMOD
The analogy used in class is worth keeping because it draws the right boundary. Import and export is trade between countries, not family. There is no relationship between the two files. Inheritance is the one with a relationship, and that comes later.
Asked live and confirmed by running it: a variable without export is private to its file. Importing everything from testutil.js and listing the names returns BASE_URL and formatUpperCaseString only. fname is not hidden or undefined, it simply is not there.
Named, default, and the curly-brace rule
A file can export many things by name, and at most one thing as the default.
// logs/logger.js
export default function log(message) {
console.log("[LOG] " + message);
}
export function logBetter(message) {
console.log("[LOGS] " + message);
}
// 157.js - default import, no braces, and the name is yours to pick
import log from './logs/logger.js';
log('Starting'); // [LOG] Starting
// the named one from the same file still needs braces
import { logBetter } from './logs/logger.js';
Asked in class: what happens if you write import log from './logger.js' when the file has no default export? You get an error, and the reason is the rule in one line: braces are for when there is no default, and no braces is for when there is. A file can offer both at once, and then you use whichever form matches the thing you want.
Aliases, and the star to avoid
Two modules can export the same name. as renames on the way in, which is what makes that survivable:
// 156_test.js - same export name from two different modules
import { BASE_URL as bul_util, formatTestName } from "./utils.js";
import { BASE_URL as bul_testtul, formatUpperCaseString } from "./testutil.js";
console.log(bul_util); // https://api.staging.com
console.log(bul_testtul); // https://app.vwo.com
console.log(formatTestName("login")); // TC_LOGIN
The alias explanation given was a childhood nickname: the same person, a shorter name for local use.
import * also exists, and the session's position on it was blunt.
Linting, defined in passing and worth writing down. A linter is a program that checks your code follows the agreed rules, and ESLint is the one you will meet. The star import is a good example of what it is for: the code runs, so only a tool that reads for practice rather than correctness will tell you it is wrong.
The wall, and the class on the other side of it
A question from the floor drove the whole second half. If a utility file has ten functions, do you write export ten times? What about a hundred?
The answer was that you do not, and the tone was not gentle about it.
This is not theoretical. It is exactly how the framework the 2x batch built is wired: one export class LoginPage, and the spec imports that single name to reach every locator and action on the page.
Why classes exist at all
Before object orientation there was procedural programming: C, COBOL, and everything as a function. For a bank you would write a balance function, an add function, a subtract function, and the data floated separately.
The objection raised in class is that this does not match the world. We live somewhere with relationships, with things inherited from a parent, with information deliberately hidden, and with the same person behaving differently in different company. Procedural code had almost no way to express any of that, so the four ideas that became object orientation are inheritance, polymorphism, encapsulation and abstraction, with classes and objects underneath them.
Blueprint and building
CAB, the mnemonic given for it: Class = Attributes + Behaviour. Attributes are the data, behaviour is the functions, and a function that lives inside a class is called a method. That is the whole definition.
The consequence to keep: a class costs no memory. Memory is allocated when you create an object from it, and each object gets its own.
What `new` really does, and what the variable holds
class Person {
#name;
#age;
eat() {}
sleep() {}
}
let pramod = new Person();
let amit = new Person();
The distinction the session was careful about: pramod is not the object. It is a reference pointing at an object. new Person() on the right is the object; the variable on the left just knows where it lives.
Two objects from the same class, so are pramod and amit the same thing? No. Run on Node 22: pramod === amit is false, while pramod instanceof Person is true. Every new builds a separate object with its own memory, and the two references point at different places.
Checked against the repository's own chapter 20 notes, and each of these is the real behaviour:
| Claim | What actually happens |
|---|---|
Person() without new |
TypeError: Class constructor Person cannot be invoked without 'new' |
Reading #name from outside |
Not possible. Object.keys() returns nothing and JSON.stringify() gives {} |
A subclass reading the parent's #name |
SyntaxError: Private field '#name' must be declared in an enclosing class |
| Using a class before it is declared | ReferenceError: Cannot access 'Later' before initialization |
That last row is worth a second look, and it connects back to the hoisting and closures class. A class is not simply un-hoisted. It is hoisted and then held in the temporal dead zone, which is why you get "cannot access before initialization" rather than "is not defined". Same behaviour as let and const, and different from a function declaration, which really can be called before its line.
The hash, and the door into TypeScript
The # in #name is the JavaScript way of marking a field private, and the reaction it got in class is the point: anyone arriving from Java or Python looks at it and asks what it is, whereas everyone recognises the word private.
That is the argument for TypeScript in one symbol.
The framing used, and it is the one to repeat in an interview: TypeScript is JavaScript plus rules. JavaScript already had object literals that look a bit like classes, but nothing enforced their shape, and at a few thousand tests that stops being survivable. A .ts file compiles to JavaScript before it runs, so the engine never sees TypeScript at all.
Running chapter 19 yourself
Chapters 19 and 20 are written up in the LearningPlaywright3x README, pushed during this session. Two things to know before you follow the run commands.
The code folders are not in the repository yet. The README documents 19_Export_Import/155.js and 20_Class_Object_OOPs/158.js and its progress tracker marks both chapters complete, but the last code directory actually present is 18_Async_Await. Today's commit changed the README and nothing else. Type the examples from this page or from the README rather than waiting for a git pull to produce them.
The second is a version trap worth understanding rather than memorising. The README says ES modules need "type": "module" in package.json or an .mjs extension. On current Node that is no longer true:
node 19_Export_Import/155.js
On Node 22.22.3 that runs fine with no package.json at all, because Node now detects a file using import and treats it as a module. On older Node the same file fails, and the error tells you the fix in the README's own words:
Warning: Failed to load the ES module: 155.js.
Make sure to set "type": "module" in the nearest package.json file
or use the .mjs extension.
A trap found while checking this, in case you take the .mjs route on an older Node. Renaming only the file you run is not enough. With 155.mjs importing a still-.js testutil.js, Node treats the dependency as CommonJS and fails with SyntaxError: Named export 'BASE_URL' not found. The entry file and everything it imports have to agree. Renaming all of them to .mjs, or adding "type": "module" once, both work.
Questions from the floor
Can I change an imported function inside the file that imported it? Yes, though the session moved on quickly. The useful version of this is that imports are live bindings to the exporting module, not private copies.
Is this like importing libraries in Selenium? Yes, close enough to be a fair mental model.
Can there be more than one default export in a file? Syntactically you would be trying to name two things "the main one". There is exactly one default per file, and no limit at all on named exports.
Do we only ever have one file to import from? No. A project has as many modules as it needs, and a single file commonly imports from several, as the alias example does.
Everything so far has been JavaScript. How do we cope when classes switch to TypeScript? By recognising there is nothing new to learn in the switch. All twenty chapters are JavaScript, and TypeScript adds type annotations and a few keywords such as private in place of #. The demonstration was to strip the annotations off a real page object until it was ordinary JavaScript again.
Will we write a class for every page? Yes. Login, inventory, item detail, cart, both checkout steps and checkout complete each get a class. That is the Page Object Model, and it is why this session existed.
Tasks and announcements
- Today's task: watch the Linux basics session on SDET Club, then write your own cheat sheet and post it in the comments. It covers more than forty commands. This came up because
./file.jsand../logs/logger.jsin an import path are just relative paths, and that tripped people up. - AI Fluency part 1 is tonight at 8:00 PM IST, part 2 on Thursday at the same time. Attend live if you can; recordings follow. The companion notes are at AI Fluency: Framework and Foundations.
- Finish Claude 101 and its certification if you have not, then post the certificate on LinkedIn. The recording sits in the SDET Masterclass and takes under an hour at 1.5x. Study guide: Claude 101.
- Claude Code 101 is targeted for the first week of September.
- Class notes board for this batch: the 3x Playwright and AI Mastery Eraser workspace.
- Next up: the constructor, then inheritance, polymorphism, encapsulation and abstraction, all in TypeScript.