I’ve been building a small Typescript + Playwright project against SauceDemo — a public site made for test automation practice — partly to get sharper with test automation tooling, partly because I wanted something to point to that’s more concrete than a resume bullet.
What this covers
The project uses TypeScript, Playwright and the Page Object Model Pattern (POM) with tests targeting SauceDemo’s login flow first. This post is Part 1a which covers just two test cases - the valid login and locked-out user case. Invalid credentials and empty-field validation are next and I’ll cover it in part 1b.
Fixtures
I’ll admit I wrote the fixture before I fully understood what a fixture was - I built loginPage in fixtures/index.ts then went back to read how Playwright defines them once I had something working.
Both test cases need to load root page and login process so I moved that setup into a Playwright fixture.
type Fixtures = {
loginPage: LoginPage;
};
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
await page.goto("/");
await use(new LoginPage(page));
}
});
I’d used beforeEach for this kind of setup, but a fixture is cleaner here. And the setup will only run when it is passed to a test as a parameter — like ({ loginPage }) => {}. My first pass had the same page-navigation logic copy-pasted at the top of each test. Pulling it into a fixture removes duplication. Instead of saying “setup the page and do X”, it reads as “given a login page then do X.”
Where I got stuck
The thing that actually confused me was await page.goto("/"); — a relative path? But relative to what? I hadn’t defined a base URL anywhere I could point to. A code review pass flagged it, and that’s how I found baseURL in playwright.config.ts
// playwright.config.ts
export default defineConfig({
use: {
baseURL: 'https://www.saucedemo.com',
},
});
Before I found that out, I’d hardcoded the full URL directly in the loginPage.ts. Now it is defined in the config file, cleaner, and it matters more once inventory and checkout page need the same base address.
Next up
Part 1b: invalid login and empty-field validation, plus a look at whether those need anything beyond what’s already there.