|

JavaScript to Playwright Advanced Framework: The Complete 21-Day SDET Guide

21-day path from JavaScript to TypeScript to Playwright to framework

This is the hub for my new 21-day series: JavaScript to Playwright Framework.

I am Pramod Dutta. I teach SDETs in India for a living. Every live batch of mine starts the same way, and it is not npx playwright test. It is Node. A .js file. console.log. Then var versus let versus const. Then hoisting. Then operators that silently fail an assertion. Then if / switch. Then loops and arrays. Then functions and closures. Then objects. Then Promises. Then a class that becomes a BasePage. Then TypeScript so that class refuses a wrong selector. Then — and only then — we launch a browser.

If you skip that climb, your Playwright suite will still run. It will also leak state, throw Temporal Dead Zone errors you cannot name, await inside a forEach that does not wait, copy a login spec five times, and fail interviews that a 20-minute JavaScript lab would have saved.

This post is the SEO hub and the map. Every day of the series lives under this URL. I will recap what each day actually teaches, from the files that actually exist on GitHub, and I will link you into that day. I will not invent a repo file. I will not polish a classroom typo. I will not pretend an empty Dockerfile is a pipeline.

If you want the video plus project path after you finish these 21 posts, the course is here: Playwright Automation Mastery. This blog series is the public spine. The course is the reviews, the live projects, and the framework we actually ship.

21-day path from JavaScript to TypeScript to Playwright to framework

Contents

This is not the 21-Day Playwright TypeScript Challenge

Scrolltest already has a live series: the 21-Day Playwright with TypeScript Challenge. That calendar starts at the Test runner. Day 1 of that challenge is install, a first spec, and TypeScript from the first hour. People land on it from Google, from the course, from a WhatsApp forward. It is a good series. It is a different series.

This series has a different slug prefix: js-playwright-framework-day-NN. This hub has a different slug: javascript-typescript-playwright-advanced-framework-21-day-guide. This series starts earlier. We earn page.goto from console.log. We earn a typed Page Object from a JavaScript class. We earn fixtures from a placeholder folder that is still skipped on main. We earn Cucumber from the same LoginPage, not from a second stack. We earn CI from a YAML that still does not run cucumber-js. We earn MCP last, not first.

If you landed here from the TypeScript Challenge calendar, stay or go back — both paths are mine — but do not mix the day numbers. Day 10 here is first Playwright tests. Day 10 there is a different lab. Different files. Different promise.

I will say this again on days that share a topic with the live challenge (codegen, MCP, traces), because Google will mix the URLs. Different slug. Different labs. Different gate.

Who this 21-day SDET path is for

This path is for the tester who can already click through a product and write a bug, and who now has to own a Playwright suite in JavaScript or TypeScript.

It is for the Java SDET who has TestNG and Rest Assured in their fingers and keeps translating every Playwright idea back into Java. You do not need a second runner to test /booking. You need APIRequestContext. Day 19 is that translation, after you have already learned why request.get is a Promise.

It is for the person who recorded a login with codegen last Tuesday and now has 40 specs that all type the password. Day 12 is storageState. Day 16 is a LoginPage. Day 17 is a fixture that constructs that page. Day 18 is a Given that should call open() — and on this branch, that Given is missing await. I will not hide that.

It is for the interviewer-side SDET who wants a map they can hand a junior: here is the language, here is the type, here is the locator rule, here is who owns the page, here is who owns the request, here is the CLI the pull request actually runs.

It is not for someone who wants a 20-minute “Playwright crash course” that starts at getByRole and ends at a green HTML report. That person should take the TypeScript Challenge, or the first weekend of the course. This hub is 21 mornings. Day 1 is 09:00 IST on 30 August 2026. Day 21 is 19 September 2026. One lesson a day. Homework that is a file, not a vibe.

You need Node.js 18 or newer. A terminal. Git. The willingness to run a file that throws, read the error, and not skip it. You do not need TypeScript on Day 1. You do not need Playwright installed on Day 1. You do not need an LLM key until Day 21, and even then hasApiKey() exists so CI can skip.

The three repos this series actually uses

I will name them once, here, so every day recap can stay short.

1. LearningPlaywrightBatch on branch main.

This is the language repo. Chapters 01 through 22 are JavaScript and TypeScript labs — 01_basic.js through 208_23_logs_Decortors.ts. Classroom spellings stay: Parrallel, sequenttialTest, Coverted, RESPOSNE, Clsss, Ovveride, Decortors, FreeTrail, 195_REAL_BRowser_Selection.ts. Empty files stay empty: 12_hoisting_if_block.js is 0 bytes, 207_Decorator.ts is 0 bytes. I skip them and I say so. The same repo later holds Lecture_Playwright_CLI, Lecture_Playwright_MCP, and Lecture_Playwright_AI_Agents. Days 1–9 and Days 20–21 live here.

2. LearningPlaywrightFundamentals on branch main.

This is the browser repo. tests/01_Basics through tests/22_Misc_Concepts, plus tests/Projects and TTACartProject. Labs 209 through 271 are the numbered specs. Folder 21_Fixture is a skipped placeholder. Folder 22_Misc_Concepts is a skipped placeholder. Folder 23_Advance_Framework is also a skipped placeholder — the real layers are not in this tree. Project 5 QA Profile is a README scaffold. There is no Task1.spec.ts in that folder. I will not invent one. Days 10–16 live here.

3. AdvancePlaywrightFramework1x on branch feat-cucumber, not main.

This is the framework repo. Config, BasePage, TTACart pages, test-base fixtures, Winston, visualStep, ApiHelper, CustomTTAReporter, Cucumber levels 00–02, Restful Booker layers, GitHub Actions, and src/ai. The Dockerfile is empty (0 bytes). src/fixtures/index.ts is empty (0 bytes). test:lor is documented and missing from package.json. Credentials fallbacks are empty strings, not demo passwords. CI runs npx playwright test and does not run cucumber-js. Days 17–21 live here.

Three clones. Three jobs. Do not look for BasePage.ts in the fundamentals tree. Do not look for chapter_02 in the framework tree. Do not look for Cucumber on main of the framework — the BDD tip is feat-cucumber.

git clone https://github.com/PramodDutta/LearningPlaywrightBatch.git
cd LearningPlaywrightBatch
git checkout main

git clone https://github.com/PramodDutta/LearningPlaywrightFundamentals.git
cd LearningPlaywrightFundamentals
git checkout main

git clone https://github.com/PramodDutta/AdvancePlaywrightFramework1x.git
cd AdvancePlaywrightFramework1x
git checkout feat-cucumber

Public demo hosts appear in these files: VWO, the-internet, SpiceJet, TTA widgets, TTA Bank, TTACart at https://app.thetestingacademy.com/playwright/ttacart/, Restful Booker. If a host is down, the spec fails. That is a classroom fact, not a Playwright bug.

How to use this hub

Read this page once. Bookmark it. Then open one day each morning at 09:00 IST.

Each day post is a full lesson: the files, the quotes, the interview checks, the FAQ, the diagram brief. This hub is the map and the recap. If you only have 20 minutes, read the day recap here, then jump. If you have 90 minutes, open the day, clone the repo, run the files, paste the output into a notes doc. The homework is the file. The recap is not a substitute for the throw.

I will link every day below, twice: once in the calendar table, once in the long recap. The slugs are stable. The day posts are drafts until they publish on their morning. This hub is itself a draft, scheduled 30 August 2026 09:00 Asia/Calcutta, same morning as Day 1. It is not published from this file. status: draft. do_not_publish: true.

When a day says a file is empty, believe the day, not a slide. When a day says CI does not run Cucumber, do not tell an interviewer the BDD track is gated. When a day says folder 21 is a skip, do not write test.extend and claim it lives in LearningPlaywrightFundamentals.

Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.

The 21-day calendar

One lesson a day. 09:00 Asia/Calcutta. Day 1 is 30 August 2026. Day 21 is 19 September 2026.

DayDate (IST)PhaseTitleSlug
130 AugJSJavaScript basics, var/let/const, hoistingDay 1
231 AugJSIdentifiers, literals, operatorsDay 2
31 SepJSif/else, switch, control flowDay 3
42 SepJSLoops, arrays, test dataDay 4
53 SepJSFunctions, closures, stringsDay 5
64 SepJSObjects, multi-dimensional arraysDay 6
75 SepJSCallbacks, Promises, async/awaitDay 7
86 SepJSOOP, inheritance, BasePageDay 8
97 SepTSTypes, interfaces, enums, typed POMDay 9
108 SepPWFirst tests, context, pagesDay 10
119 SepPWLocators: role, CSS, XPathDay 11
1210 SepPWstorageState, Allure, web tablesDay 12
1311 SepPWFrames, keyboard, drag-drop, alertsDay 13
1412 SepPWSVG, Shadow DOM, upload, downloadDay 14
1513 SepPWAssertions, hooks, data-driven testsDay 15
1614 SepPWPOM, fixtures, course projectsDay 16
1715 SepFWAdvanced framework layersDay 17
1816 SepFWCucumber BDDDay 18
1917 SepFWAPI: Restful Booker, JSONPath, AjvDay 19
2018 SepFWCLI, codegen, trace, CIDay 20
2119 SepFWMCP, AI agents, capstoneDay 21

Four phases. Remember them as one sentence: language, then types, then the browser, then layers.

  • Days 1–8 — JavaScript. The language an SDET actually types. No Playwright install.
  • Day 9 — TypeScript. The contract. Still no spec run.
  • Days 10–16 — Playwright Test. Browser, locators, tables, frames, expect, POM. Fundamentals repo.
  • Days 17–21 — Framework. Layers, Cucumber, API, CLI/CI, MCP and agents. feat-cucumber.

Phase 1 — JavaScript for SDETs (Days 1 to 8)

Playwright is a Node library. Your spec file is JavaScript that Node executes. test(), expect(), page.goto(), a custom fixture, a storageState path, a const config object — all of that sits on top of the same engine rules you meet in chapter_01_Basics through chapter_17.

I have interviewed enough SDETs to see the pattern. Someone can write a login test with page.goto(baseURL) and still cannot explain why console.log(a) prints undefined with var and throws with let. That gap shows up later as a var i inside a retry loop that leaks into the next test, a let token used above its declaration, a helper written as const login = async () => {} and then called above the line, or await inside forEach that does not wait.

Days 1 through 8 are the fix. We stay on LearningPlaywrightBatch. We do not install Playwright. If that sentence makes you restless, you are the person this phase is for.

Day 1 — JavaScript basics, setup, var/let/const, and hoisting

Read: Day 1: JavaScript Basics for SDETs — Setup, var/let/const, and Hoisting

Day 1 is Node, a terminal, and the two-phase engine.

You clone LearningPlaywrightBatch, stay on main, and run chapter 01: 01_basic.js, 02_JS_Step_By_Step.js, 03_verify_setup.js, 04_hot_code.js. You print process.platform, process.arch, and process.version. On an Apple Silicon Mac, process.arch should later print arm64. That is exactly what 03_verify_setup.js is for. You feel why a 10,000-call add is “hot” and still not your suite’s bottleneck.

Then chapter 02, the folder named chapter_02_Java_Concepts: comments (05_Core_Comments_JS.js, the Ctrl+/ habit in 08_Lab.js), identifier rules (06_Core_Identifier_JS.js), and the file I want every junior to fail on purpose — 07_var_let_const.js. const by default. let when the value must change. var never in new code. const does not freeze an object; it freezes the binding. That sentence returns on Day 6.

Hoisting is the rest of the morning. 09_Hoisting.js sketches two phases: memory, then execution. 10_hoisting_var.js is the greeting version. 13_hoisting.js puts noise between the logs. 11_hoisting_function.js is function-scope hoisting inside a helper. Then the Temporal Dead Zone: 14_let_hoisting.js, 15_let_block.js, 17_rogit.js. var inside if or for leaks (16_var_if_loop.js). let in the same place throws if you touch it early.

Skipped on purpose: 12_hoisting_if_block.js exists on main and is empty (0 bytes). I do not invent an if-block lab for it. The same idea is in 15, 16, and 17.

Why Playwright cares: fixture state, loop indexes, helper functions, and const config all obey hoist and scope. Locator skill does not debug a TDZ error. A const login = async () => {} called above its line is a TypeError. A function login() declaration would have been hoisted. That triad — works / TDZ / TypeError — is the entire function-hoisting interview.

Homework is not optional. Uncomment the dangerous lines. Read the error. Re-comment. Do not skip the error.

Day 2 — Identifiers, literals, and operators

Read: Day 2: JavaScript Identifiers, Literals, and Operators for Test Automation

Day 2 is the day your assertions stop lying to you.

Identifiers cannot be reserved words. Do not shadow undefined, Infinity, or Playwright fixtures like page. 19_Identifier.js asks whether you can let undefined = 5. Yes. Should you? Never.

Literals are the values you type. "", " ", and "0" are three different things. Only "" is falsy. The three string literals that break assertions are the empty string, the space, and the string that looks like a number. getAttribute returns a string or null. "false" == false is true in loose equality. Stay strict.

Null versus undefined is the SDET version of a contract. null is intentional emptiness — the API returned {"middleName": null}, localStorage.getItem missed, you set selectedRow = null. undefined is absence — declared only, missing JSON key, skipped argument. null == undefined is true. null === undefined is false. expect is strict. expect(null).toBeUndefined() fails. People write it when they used == in the console, saw true, and assumed Playwright agrees.

= assigns. == coerces. === checks type and value. There is no !===. 25_IQ.js is the interview file. "0" == false is true. NaN == NaN is false. Loose equality is not transitive: "" == 0 and "0" == 0 do not make "" == "0". expect().toBe() lives on Object.is. That is why Day 2 exists before Day 10.

Operators you actually use in a suite: % and ** in sharding and backoff, += in retry budgets, + that concatenates the moment a string appears. && / || / ! short-circuit and follow truthiness. CI=false is still a truthy string. Ternary is for values. if is for actions. typeof null is "object". typeof [] is "object". Use Array.isArray and null checks.

Nullish coalescing is the test-data operator. ?? preserves 0 and "". || does not. Number(process.env.RETRIES || 2) is a production trap: after you have parsed to a number, 0 || 2 is 2. Parse first, then default: Number(process.env.RETRIES ?? 2), and reject NaN.

Day 15 will hand you CSV cells that are "", "0", null, and missing keys. Day 17 will hand you STANDARD_USER ?? ''. You will be glad you did 25_IQ.js with a notebook, not a highlighter.

Day 3 — if/else, switch, and real test branches

Read: Day 3: JavaScript Control Flow — if/else, Switch, and Real Test Branches

A statement is a decision, not a sentence. Day 3 takes classroom labs 33 through 52 and turns them into the branches you actually write: API status, environment, role, and browser.

Lab 33 is the smallest useful branch. Lab 34 is else if as a ladder, not a pile of ifs. Lab 35 is nested if the way real products work — login, then role, then feature flag. Lab 36 is API status branching, and it steps on the Day 2 landmine: "200" == 200 is not how you want to live. Lab 37 is what is truthy inside if (). An empty array is truthy. 0 is falsy. "" is falsy. "0" is truthy. Lab 38 is AND, OR, and the locked account. Lab 39 is the missing-brace interview. Lab 40 is the empty helper — the real lesson is a function that looks like a branch and does nothing. Lab 41 packs three interview cases in one file.

Then switch. When the value is discrete, stop stacking else if. Lab 42 is switch with break, the happy path. Lab 43 is named “with Break” and has no break — fall-through is the bug. Lab 44 is default for the value you did not plan. Lab 45 is switch on a real API status. Lab 46 is grouped cases, the browser one you will copy: Chromium family versus Firefox versus WebKit. Lab 47 is fruit-salad fall-through. Lab 48 is switch (true) for ranges, used sparingly. Lab 49 is duplicate case, first one wins. Lab 50 is the Day 2 reminder: switch uses ===, so "5" is not 5. Lab 51 is 0 is not false inside switch. Lab 52 is the user-input stub, finished as env input.

The rule I want you to memorize: if / else if for ranges, predicates, and mixed conditions. switch for a discrete value you can list. switch (true) is a trick, not a house style. Playwright later: status code in API setup, environment in config, browserName for engine families, test.skip for one browser or one env. An empty array is still truthy. A missing break still runs every case after the one you wanted.

Day 4 — Loops, arrays, and test data

Read: Day 4: JavaScript Loops and Arrays for Test Data and Assertions

A test that cannot walk a list is a one-shot recording. Day 4 is labs 53 through 75.

Increment is the engine of every loop. a++ and ++a print different values because one returns the old number. Labs 54 and 55 make you feel that. for is Init, Condition, Update (56, 57, 58). while is the same three pieces written as a sentence (59, 60). do-while runs the body once, then asks (61, 62). Pick the loop on purpose. for when you know the count. while when you are waiting for a condition. do-while when the body must run once — a first poll, a first screenshot.

Then arrays. Create the list the way a tester means it (63). Access, at(-1), modify in place (64). Add and remove from the ends, then splice the middle (65). Lab 66 is a real browser list, a leftover require, and a loop that talks. Search returns an index, a boolean, or the element (67). Four ways to walk, and one I do not want on arrays: for...in (68). for...of is for values. for...in is for keys, and on an array it will surprise you with indexes as strings and inherited junk.

map, filter, reduce, flat are the assertion toolbox (69). sort will betray numbers unless you bring a comparator (70) — 11 before 2 if you sort as strings. slice does not mutate; concat and spread build; join reports (71). Array.isArray, every, some (72). Copy versus the same box with two names (73, 75) — changing a “copied” array that is actually a shared reference changes the original. That is the Day 6 object lesson arriving early. Destructure the first items, rest the tail (74).

Playwright payoff in the same morning: locator lists, table rows, data-driven assertions. locator.all() is an array of locators. Walk it with for...of and await. Do not forEach plus await. Day 7 will make that a crime. Day 12 will read Helen Bennett’s country from a table with the same shape. Day 15 will register one test per CSV row with the same loop-outside-the-test rule.

Day 5 — Functions, closures, and strings

Read: Day 5: JavaScript Functions, Closures, and Strings for SDETs

Functions stop copy-paste. Closures remember retries. Strings write the expect message CI actually reads. Day 5 is chapters 09 and 10, labs 76 through 107.

Four function types: no argument no return, argument no return, no argument with return, argument and return (77–80). Function expressions (82). Arrow functions (83) — the form you will type in test('name', async ({ page }) => {}). Three styles, one API check (84). IIFE (85) — useful as a classroom idea, rarely needed in a Playwright repo because a module is already a scope. Default parameters (87). Rest parameters (88). Lab 89 is the hoisting TypeError, and the capital .JS filename. Spread (90). Return values (91). Function hoisting (92). Scope (93).

Closures are the heart. Lab 94 returns the inner function. Lab 95 is a counter object. Lab 96 is a retry tracker. Lab 97 is a rate limiter. A module-level retry counter is a bug: the next test inherits the count. A closure per call is the fix. Higher-order functions (98) take or return a function — map, a wrapper around a test body, a message factory. Pure functions (99) do not touch the outside world. Helpers that format an error string should be pure. Helpers that click Login cannot be. Callbacks (100, 101) set up Day 7.

Strings: quotes, templates, String() (102). Length, index, at, charCode (103). Search and check (104) — indexOf returns -1, not false, when the substring is missing. Slice and substring (105). Transform, replace, split, join (106). Conversion and immutability (107). A string assertion fails when the UI “looks correct” because you compared "Dashboard " to "Dashboard", or you used toBe on a concatenated message that had a different space. expect takes a message. Use it. Closures make the message factory. That is the Playwright payoff of Day 5, before we have Playwright.

Day 6 — Objects and multi-dimensional arrays

Read: Day 6: JavaScript Objects and Multi-Dimensional Arrays in Test Automation

An object is a bag of keys (108). const does not freeze the bag (109) — you can still grow a config, overwrite a field, delete a key (110). Primitive versus reference is the file I tell people to read twice (111). Two variables can point at the same box. expect(body).toBe(expected) fails when the fields match because toBe is identity. toEqual walks the fields. Day 15 will say this again. Day 2 already hinted.

A property is more than a value (112) — descriptors, writable, enumerable. Methods, this, and a chain (113). Destructure the body, do not pick keys by hand (114). Spread is a shallow copy, and this still exists (115). Getters look like fields, setters look like assignment (116). Object.keys, values, entries, for...in (117). Three objects you will actually ship (118): a config, an API body, an expected body. let can rebind, const cannot, both can mutate (119).

Then the map to Playwright: JSON fixtures, API request bodies, expected bodies. A fixture object you mutate in test A is still mutated in test B if you shared the reference. Clone it. Or build it per test. Day 17’s DataGenerator.checkoutCustomer() is a factory for this reason.

A table is an array of arrays (120). Index, mutate, last cell, three ways to walk (121). map, reduce, and find the fails (122). Labs 123–125 are nested loops with a rule: right triangle, inverted stack, pyramid. Those look like school. They are the muscle for walking a web table by row and cell. Day 12 is Helen Bennett. Day 6 is why you can say rows[i][j] without blinking. fullName becoming "PramodDutta" with no space is a getter that concatenated without a separator — a classroom bug I keep because it teaches you to read the getter, not the field names.

Day 7 — Callbacks, Promises, and async/await

Read: Day 7: JavaScript Callbacks, Promises, and Async/Await for Playwright

Playwright is a Promise library wearing a browser. Day 7 is the most important language day. If this day is foggy, Days 10 through 21 will look like magic.

Callbacks. A callback is a function you hand over (126). Playwright’s test(), page.on("dialog", cb), and afterEach are callbacks. A sync callback finishes before the next line (127). forEach is a sync callback. It will not wait. Do not await inside forEach over locator.nth or request.get. Walk with for...of. An async callback runs after the rest of the file (128). setTimeout does not pause the file. A, C, then B. Callback hell is a VWO login written as nests (129). The Pyramid of Doom without the VWO comments is 132. Page Object methods must return Promises, not take cb.

Promises. A Promise is an object you can print (133). page.goto returns Promise<Response>. Logging it does not unwrap it. .then runs only on resolve (134). .catch runs only on reject (135). Playwright APIRequestContext does not reject on HTTP 500. It resolves to an APIResponse. status() can be 500. Throw when !response.ok() if you want lab 135. .finally always runs — like afterEach (136). The VWO login as a Promise chain (137) — return the next Promise inside .then or the chain is a lie.

Promise.all is fail-fast parallel (138) — click plus waitForResponse, or three health GETs that must all be up. Create the waiter first, then fire the action. Promise.allSettled is a test report (139) — smoke three services, print every row including the 500. Promise.race is first settler (140) — and the classroom file does not actually delay, because the 100 / 500 sit outside setTimeout. I keep that bug. Prefer Playwright’s own timeout over a hand-rolled race.

async/await. .then versus await, and the missing token (142) — pass the auth header into the next request.get. The login chain converted (143); filename stays Coverted. async always returns a Promise (144). try/catch/finally is then/catch/finally (145). Sequential when step 2 needs step 1 (146); filename stays sequenttialTest. Parallel when it does not (147); filename stays Parrallel. Interview file (148): forgot await page.goto — assertion is D, navigation is C. A flaky API and a retry loop that does not break (149) — bounded, throw if every attempt failed, return on first ok(). Do not retry a POST that creates a booking unless the API is idempotent. Three retries of POST /booking is three bookings.

Day 19 will climb from raw request.get to ApiHelper to a fixture that owns the token. Every step of that climb is a Promise you learned to await today.

Day 8 — OOP, inheritance, and BasePage

Read: Day 8: JavaScript OOP and Inheritance — From Class to BasePage

A Page Object is not a Playwright feature. It is a class that extends a base. Day 8 takes chapters 16 and 17 and turns them into the BasePage → LoginPage shape you will write for the rest of this series.

A class is attributes plus behaviour (153). Constructor, this, and new (154). Two objects, and method versus function (155) — a method is a function that belongs to an object and uses this. A Browser class with a method body that lies (156). Two API clients, one class (157).

Private, public, static. #apiKey is hidden; user is not (158). cred.apiKey is undefined because the field is #apiKey. Static belongs to the class (159, 160) — a default timeout, a PATH constant, a counter you should not put on the instance if it is shared. Encapsulation hides the field and guards the door (161–163). A setter that can say no is the first validation you will put on a config object.

Export and import is how a class leaves its file. Named import from testutil.js (150). Two modules, same name, as (151). Default import of the logger (152). This is the muscle for import { LoginPage } from './LoginPage' on Day 16 and import { test } from '@fixtures/test-base' on Day 17.

Single inheritance: LoginPage extends BasePage and defines nothing (164) — the child gets the parent’s methods. super() in the constructor (165). Method overriding: whoever’s object is present (166). super.setup() then the extra step (167). One list, three execute() implementations (168). Page Object verify() — the POM you came for (169). The same idea for reports (170).

Multi-level: BasePage → AuthPage → AdminPage (171). JavaScript will not extends A, B (172) — mixins are the honest answer, multiple inheritance is not. Hierarchical skeleton (174) — I will not fill a file the classroom left as a sketch. Then the three files that become the pattern: Basepage.js (named export of the parent; classroom capital P), LoginPage.js (import parent, export child), 173_Test_2.js (the spec).

Day 16 will construct new LoginPage(page) with no parent. Day 17 will give you an abstract BasePage with page, el, log, and protected goto(). The idea is today’s. The Playwright Page type is tomorrow’s — Day 9 types it, Day 10 hands it to you.

Phase 2 — TypeScript is the contract (Day 9)

Day 9 — Types, inter