level 01 / modules-and-imports
Modules & Imports
ES modules, barrel exports, and the import patterns that keep Playwright projects organised — from page objects to shared fixtures and test data.
ES modules in TypeScript
TypeScript uses ES module syntax. Each file is a module; names are private by default and must be explicitly exported.
// math.ts — named exports
export function add(a: number, b: number): number {
return a + b;
}
export const PI = 3.14159;
export interface Point {
x: number;
y: number;
}
// Default export — only one per file
export default class Calculator {
add(a: number, b: number) { return a + b; }
}
// main.ts — importing
import Calculator, { add, PI, type Point } from './math';
// Namespace import — all named exports under one object
import * as math from './math';
math.add(1, 2);
// Type-only import — erased at runtime (zero JS output)
import type { Point } from './math';
Barrel exports
A barrel (index.ts) re-exports everything from a directory, creating a
single import point:
// pages/index.ts — barrel
export { LoginPage } from './login.page';
export { InventoryPage } from './inventory.page';
export { CheckoutPage } from './checkout.page';
// test file — clean single import
import { LoginPage, InventoryPage, CheckoutPage } from '../pages';
Key insight: Barrels are convenient but can cause circular dependency issues and slow TypeScript project startup. For large projects, prefer direct imports. Use barrels for public library APIs where import ergonomics matter.
Playwright import patterns
// pages/login.page.ts
import type { Page } from '@playwright/test';
export class LoginPage {
constructor(private readonly page: Page) {}
async goto(): Promise<void> {
await this.page.goto('/');
}
async signIn(username: string, password: string): Promise<void> {
await this.page.getByPlaceholder('Username').fill(username);
await this.page.getByPlaceholder('Password').fill(password);
await this.page.getByRole('button', { name: 'Login' }).click();
}
}// fixtures/test-fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
type Fixtures = {
loginPage: LoginPage;
};
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},
});
export { expect } from '@playwright/test';// tests/login.spec.ts
import { test, expect } from '../fixtures/test-fixtures';
test('standard user can log in', async ({ loginPage, page }) => {
await loginPage.goto();
await loginPage.signIn('standard_user', 'secret_sauce');
await expect(page.getByText('Products')).toBeVisible();
});tsconfig.json path aliases
Path aliases replace relative import hell ('../../../pages/login.page') with clean absolute paths:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@pages/*": ["src/pages/*"],
"@fixtures/*": ["src/fixtures/*"],
"@data/*": ["src/test-data/*"]
}
}
}
// Before path aliases
import { LoginPage } from '../../../pages/login.page';
// After path aliases
import { LoginPage } from '@pages/login.page';
Note: Playwright requires a require mapping in playwright.config.ts to
match the tsconfig paths (via tsconfig-paths or native Node 18+ support).