Where this sits
The previous class finished the fixtures and got two end-to-end specs running. The framework is now roughly 80 to 90 percent built, and that changes what comes next:
Now it is time to start using GitHub Copilot or Claude Code, because the base framework is already ready. On top of it, extra things can be added very easily.
Today adds one small feature, reading credentials and checkout data from a .env file, and spends most of the hour on how it gets added rather than what it is.
The reason is blunt, and it came from the other side of the interview table:
I am also taking interviews, and this is the problem I have very commonly found. If I ask how you added the environment variable, they say "sir, I just used a command in GitHub Copilot, add the env support". That is not the answer we wanted.
The formula
The whole method fits in nine steps, and the session ends by writing them into the repository's learnings folder.
Plan mode, not build mode
The first prompt does not ask for code. It asks for a written plan, and it names what the plan must contain:
Create a temporary plan for it, where you will mention the files touched and the files changed, and give me all the details of what changes you are going to make.
The agent came back with questions first, which is the behaviour you want. The plan it then produced was genuinely useful:
- dotenv was already installed. It was in
devDependenciesand already imported byplaywright.config.ts. Nothing to add. - Two alternatives were considered and rejected, both encryption-capable wrappers around dotenv. Not needed here.
- It listed the files it would touch and, importantly, the files it would leave untouched.
Plan mode is not a Claude Code feature. Amazon Q has one too. The point is not the tool, it is refusing to let anything be written until you have read what it intends to write.
Reading the plan critically, twice
Here is where most people stop. The plan looked fine. It was approved by nobody.
Push-back one, on structure. The plan put the env-reading helper inline in the spec file:
Can we create some util which can make it more modular in nature, and optimise it with minimum changes?
The reply was "good call", and the helpers moved out into their own module. That is the right instinct generalised: a helper written inside the file that uses it is a helper you will write again tomorrow.
Push-back two, on damage. The second question is the one that earns its keep:
Can you please check if other spec files do not break due to these changes? And make sure we add the files without too many comments.
That found a real problem the first two passes never mentioned.
The blast radius, and the CI break
.env is in .gitignore. That is correct and it is also the bug.
GitHub Actions checks out a repository with no .env file. A fail-fast check that runs when the module loads therefore throws at collection time, before any test runs, which fails the entire suite rather than the one spec that needed the file.
The repository's own note records how that was confirmed, and it is worth copying as a technique:
mv .env .env.bak && npx playwright test
All five tests died. The fix is a CI step that seeds the file from the committed example:
cp .env.example .env
which also makes .env.example load-bearing rather than decorative.
Asked in the room, and worth thinking about before reading on: if a check throws while the module is being imported rather than inside a test body, how many tests fail? All of them in the run, because the failure happens during collection. Moving the check inside the test body would scope the failure to one test, but it also delays the signal. The trade taken here was to keep the fail-fast check and seed the file in CI.
What actually shipped
The util landed at src/config/env.ts and exports three readers:
/** Reads `.env` values. Importing this module loads `.env` into process.env once. */
import dotenv from 'dotenv';
dotenv.config({ quiet: true });
export function requireEnv(key: string): string { ... } // throws when unset
export function envOr(key: string, fallback: string): string { ... }
export function assertEnv(...keys: string[]): void { ... } // reports all missing keys at once
Two differences between the session and the repository. The util was named environment.ts on screen and shipped as src/config/env.ts. And the deeper reason it has to be a module was never stated in class, only its benefit. It is worth knowing, because it is a genuine trap.
Fail fast, or fall back, per value
The shipped design does not pick one strategy for everything. It picks per value, which is the detail worth stealing.
| Value | Reader | Why |
|---|---|---|
| Checkout item id | requireEnv |
There is no sensible default. A wrong product silently tests nothing |
| Login user and secret | envOr |
The practice app has known defaults, so a missing file is not fatal |
| Checkout first name, last name, postal code | envOr |
Falls back to generated data, so the spec still runs |
In the spec, that reads as:
assertEnv('STANDARD_USER', 'TTA_SECRET');
const ITEM_ID = requireEnv('CHECKOUT_ITEM_ID');
and in the generator:
firstName: envOr('CHECKOUT_FIRST_NAME', generated.firstName),
dotenv is left at its default override: false, which means a real shell or CI variable beats the file. That is the behaviour you want: the file is for your laptop, the environment is for the pipeline.
Approving, and then proving
Approval was manual, file by file, not auto-accept. Each diff was read aloud before being accepted, and the running commentary was the point:
Now, if you can see, I am not a blind AI user. First the library, then the env module was created, then the data generator changed, then the credentials file.
After the changes, the existing specs were re-run as a regression, including the parallel ones, and all passed.
Then one more step, which the repository records and the session did not spell out. A spec that reads .env and a spec that ignores it look identical when the file happens to contain the same values as the defaults. So the injection was proved by overriding it from the shell and watching the run change:
CHECKOUT_FIRST_NAME=EnvProof npx playwright test
That is the difference between believing a feature works and knowing it does.
Explaining it afterwards
Two things get produced at the end, and neither is framework code.
An explainer. A skill called EL5, explain it like I am five, generates a temporary HTML page describing what was added and why, plus a hand-drawn-style diagram. It goes in a folder that is not committed with the framework, and its audience is the person reviewing your pull request:
The person reviewing your PR also does not understand what you have done. They will also try to use AI. Rather than that, show them this.
A learnings note. The formula and the findings go into a learnings folder, kept deliberately separate from docs. Learnings are for you; docs are for the framework.
The reason both exist is the same reason the whole class exists: if you cannot explain how a feature was added, you have not added it. That is the sentence to remember from this session.
Skills into the repository
The last few minutes add skill files from the Playwright skill pack into the .claude and GitHub Copilot directories, so the agent has repeatable procedures rather than fresh guesses.
Two rules were attached, and both are judgement calls the AI could not make for you:
- Only the skills that belong to this framework. A Jira requirement analyser and a test plan generator are useful, and they are not part of a Playwright automation framework. Keep those separate.
- Modify every skill before you keep it. The pack ships generic skills. A page object creator that does not know your page object conventions will produce something you then have to rewrite.
Tasks and announcements
Before the next class
- Watch the Web Fundamentals for API testers video. This is a prerequisite, not a suggestion: the next class starts API testing, Postman, and automating RESTful Booker. The video is being shared, and emailed if possible.
- Reach framework parity within 7 to 10 days: the end-to-end tests, the skills, and every file. The reasoning given is practical, that you cannot run something in Docker later if you never wrote the end-to-end test now.
- Post your Claude Code 101 certification on LinkedIn, ideally around 9 in the morning when the most people see it.
Coming later
- API testing, starting next class: fundamentals, Postman, RESTful Booker, and API fixtures.
- A flaky test analyser agent, which finds the root cause rather than asking you to maintain around it.
- Sharding and workers, in a session of their own. Docker was already covered in the SDET master class.
- An AWS certification roadmap was requested; the answer is coming on the SDET club thread after some research.