The Testing Academy · Class Notes Saturday, 12 September (IST)
Live class · study guide

Five annotations, one grouping keyword, and the start of finding elements

The whole course laid out as a folder tree first, so you can see where every remaining class lands. Then the browser, context, page recap with three roles running side by side, the context options that let one test pretend to be a phone in Paris, the five test annotations and what each one actually does to a run, test.describe and how to run a group by name, and the opening of the topic the rest of the month is built on: the locator strategy.

By Pramod Dutta, The Testing Academy. Study notes from the live Playwright 3x class, rebuilt from the session recording and the batch repository, which was restructured into the 23-topic curriculum during the session and received the annotation, options and locator specs at the end of it. The Eraser deck was not reachable while this page was written, so the code is quoted from the repository. Both annotation specs were run on the installed Playwright before publishing, and the counts quoted for test.only and for the describe group are the real ones from those runs.

01

Where this sits, and where everything else lands

Before any code, the whole course went up as a folder tree, created live. Twenty-three numbered topics, and the point of showing it now is that every class for the next month lands somewhere on this list:

Text
01_Basics                     09_Frame_Iframe               17_Expect_Assertions
02_TestAnnotations            10_Keyboard_Hover_Drag_Drop   18_Test_hooks
03_Locator_Commands              _Calender                  19_Data_Driven_Testing
04_Session_Storage            11_JS_Alerts                  20_Page_Object_Model
05_Allure_Reporting           12_Handle_SVG                 21_Fixture
06_Multiple_Element_Filter    13_Shadow_DOM                 22_Misc_AI_Concepts
07_WebTables                  14_FileUpload                 23_Advance_PW_Framework
08_Web_Select_Frames_Iframe   15_File_Download
                              16_Scroll_toElement

Two of those fan out further. 22_Misc_AI_Concepts holds Playwright MCP, the Playwright CLI, Playwright AI agents, Selenium to Playwright migration and the 36-skills masterclass, and those run as extra evening sessions rather than in the morning slot. 23_Advance_PW_Framework is the finale, and it has its own order: API testing, then Cucumber, then the AI agent factory, then CI/CD.

MORNING CLASSES: TOPICS 01 TO 21 Basics, annotations, locators tables, frames, alerts, uploads, assertions, hooks, data-driven, page objects, fixtures THEN TOPIC 23, IN THIS ORDER 01 API testing 02 Cucumber 03 AI factory 04 CI/CD Cucumber is optional; not every team uses it Jenkins, GitHub Actions the framework running on aschedule, not on your laptop TOPIC 22, AS EXTRA EVENING SESSIONS Playwright MCP · Playwright CLI · Playwright AI agents · Selenium to Playwright migration · 36 skills masterclass
Every remaining class has a folder waiting for it. The AI topics are deliberately parked off the main track and run in the evening.
02

Browser, context and page, once more with three roles

Last class covered the model; this one used it. The hierarchy and its cleanup order came back as an interview question: create browser, then context, then page, and close in reverse, page, context, browser.

TypeScript
let browser: Browser = await chromium.launch({ headless: false });
let context1: BrowserContext = await browser.newContext();
let page: Page = await context1.newPage();

// Cleanup - reverse order
await page.close();
await context1.close();
await browser.close();

That is the raw library. Inside the test runner you ask for a fixture instead, and the choice between two of them is the whole decision:

Asked in class: how do I know when to use the page fixture and when to build contexts myself? One role, use page. More than one role at once, take browser and make a context per role. That is the entire rule.

Three roles, three contexts, three pages, all live at the same time and sharing nothing:

TypeScript
test("BCP - in app.vwo.com two roles", async ({ browser }) => {
    let adminContext = await browser.newContext();
    let userContext = await browser.newContext();
    let guestConetxt = await browser.newContext();

    let adminPage = await adminContext.newPage();
    await adminPage.goto("https://app.thetestingacademy.com/playwright/");

    let userPage = await userContext.newPage();
    await userPage.goto("https://sdet.live");

    let guestPage = await guestConetxt.newPage();
    await guestPage.goto("https://scrolltest.com");

    await adminPage.close();
    await userPage.close();
    await guestPage.close();
});

There is no limit on contexts, and no limit on pages inside one context. The run in class hit a typo on the third context name, was fixed on the spot, and the file keeps the misspelling guestConetxt as it was typed.

async goes on functions, await goes on statements. This came up twice. page.goto() returns a promise, which is why it needs await; the arrow function that contains it is what gets async. The return type of goto is itself an interview question worth being able to answer without hesitating.

03

Context options: one test, a phone in Paris

A context is not only isolation, it is configuration. Everything a real browser session carries can be set when you create it:

TypeScript
test('context with options', async ({ browser }) => {
    const context = await browser.newContext({
        viewport: { width: 1920, height: 1080 },
        locale: 'fr-FR',
        timezoneId: 'Europe/Paris',
        geolocation: { latitude: 48.8566, longitude: 2.3522 },
        permissions: ['geolocation'],
    });
    const page = await context.newPage();
    await page.goto('https://app.vwo.com/#login');
    await context.close();
});

And the same mechanism gives you a phone without a phone:

TypeScript
test('mobile context', async ({ browser }) => {
    const iPhone = {
        viewport: { width: 375, height: 667 },
        userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)',
        deviceScaleFactor: 2,
        isMobile: true,
        hasTouch: true,
    };
    const context = await browser.newContext(iPhone);
    const page = await context.newPage();
    await page.goto("https://app.vwo.com/#login");
    await context.close();
});

Run both and you watch one window open at full HD in French and another at phone size. Viewport is the window size, not responsiveness. deviceScaleFactor is zoom, how stretched the pixels are.

04

The five annotations

An annotation is a modifier on test. Five of them, and each does something different to a run:

DOES IT RUN? WHAT THE REPORT SAYS WHY YOU REACH FOR IT test.skip skipped, never executed not ready yet, park it test.fixme skipped too, but flagged as broken flaky, I will fix it later test.fail runs; green only if it really fails a known bug you have logged test.slow runs with three times the timeout slow page, slow elements test.only this one runs, the rest do not debugging, and dangerous to commit
Two of them do not run at all, and the difference between those two is intent: skip is "not now", fixme is "this is broken".

The file written in class has one of each:

TypeScript
test.skip('checkout with PayPal', async ({ page }) => {
  // never executes
});

test.only('login as Pramod', async ({ page }) => {
  // only this test runs, everything else in the file is ignored
});

test.fail('cart total is wrong, BUG-451', async () => {
  expect(90).toBe(100);   // actually returns 90
});

test.fixme('upload 2GB file', async () => {
  // skipped, but flagged as "needs fixing"
});

test('full regression report', async () => {
  test.slow();
  console.log(test.info().timeout);   // 90000 instead of 30000
});

test.fail reads backwards the first time. It does not make a test fail. It says "I expect this to fail", so Playwright runs it and reports passed when it does fail. A green tick there means the bug is still present. If the test starts passing, that annotation turns red and tells you the bug is fixed.

test.only is the dangerous one. Running that file gives 1 passed, out of six tests in it. Everything else silently did not run. That is exactly what you want while debugging and exactly what you must not commit, which is why the quality gate in the 2x batch makes a committed test.only an error.

test.slow() triples the timeout. With no timeout set in playwright.config.ts, Playwright's default of 30 seconds applies, so a slow test gets 90 seconds. That is where the 90000 in the comment comes from.

The scenario that ties them together, as told in class: a hundred tests, ninety-seven passing. Of the three failing, two are flaky and get fixme because you intend to fix them. The third fails because there is a real logged bug, so it gets fail and stays green until someone fixes the product.

Conditional, by browser

An annotation can take a condition and a reason, which is how one test opts out of one engine:

TypeScript
test('mobile layout', async ({ page, browserName }) => {
  test.fixme(browserName === 'webkit', 'Safari renders menu wrong');
  await page.goto("https://sdet.live");
});

browserName is a fixture like page. WebKit is Safari's engine, so this runs everywhere except there.

05

test.describe, and running a group by name

test.describe takes a title and a function, and everything inside becomes a group:

TypeScript
test.describe('Login Page', () => {
    test('valid credentials', async ({ page }) => { ... });
    test('invalid password',  async ({ page }) => { ... });
    test.fixme('1checkout with PayPal', async ({ page }) => {});
    test.skip('checkout with PayPal',   async ({ page }) => {});
});

Each test still gets its own page; grouping is about organisation and about being able to run the set:

Terminal
npx playwright test -g "Login Page"
Text
Running 4 tests using 4 workers
  2 skipped
  2 passed

Four tests collected, the fixme and the skip skipped, the two real ones passed. The -g flag matches on the title, so the group name is a handle you can use from the command line and later from CI.

06

The locator strategy starts here

The last stretch opened the topic the rest of the month sits on. A locator strategy is how you find an element so you can interact with it, and Playwright gives you two families:

FAMILY 1: THE COMMON ONES CSS selectors and XPath the ones that carry over from Selenium page.locator('[data-test="username"]') page.locator('//div[@id="main"]//button') FAMILY 2: PLAYWRIGHT'S OWN The getBy family describe what the element is to a user getByRole, getByText, getByLabel, getByPlaceholder, getByTestId The goal either way: the smartest, most stable locator, so the run does not break the next time the page changes.
Two families, one goal. Which to reach for, and in what order, is the next month of classes.

And the command every test opens with turns out to have more to it than a URL:

TypeScript
await page.goto(url, { timeout: 30000, waitUntil: 'load' });

waitUntil tells Playwright which moment counts as "loaded" before it moves to the next line, and it has four settings: commit, domcontentloaded, load and networkidle. What each one means, when to use which, and the timeout and referer options alongside them, are the whole of the next class.

Interview warning, delivered from the hiring side. Candidates who lean entirely on Copilot and Claude are being rejected, including one with seven years of experience who could not write a simple Playwright test when asked, and asked for an AI tool instead. The instruction for this batch: turn the suggestions off and write it from scratch until the advanced framework is built. After that, use AI for everything.

07

Tasks and announcements

Before the next class

  • Complete the exercises up to 225. That is through 03_Locator_Commands/225_LC.spec.ts.
  • Finish the previous task, the deep research on Playwright architecture, if you have not. Many had not.
  • Post questions in the doubt thread. Meeti is posting today's task to the group.

If you missed it

Rewatch last class before this one makes sense: browser versus context versus page is a full class on its own, and several questions in this session were answered there.

Coming up

Next class goes deep on page.goto options: the four waitUntil settings, timeout and referer, then further into the locator strategy. Extra evening sessions on Playwright MCP, the CLI, AI agents and Selenium to Playwright migration are being scheduled for the coming week, likely Wednesday or Friday, with dates to be announced.

Repository: LearningPlaywrightFundamentals3x was restructured into the 23-topic tree during the session, and received 220_BCP.spec.ts, 221_TA.spec.ts, 222_Test_Options.spec.ts, 223_TestAnnotations.spec.ts, 224_TestDescribe.spec.ts and 225_LC.spec.ts.