level 14 / framework-design-questions

Framework Design Questions

Master the most common framework architecture interview questions — POM, fixtures, helpers, and test structure decisions.

What Interviewers Are Testing

Framework design questions assess whether you can build a maintainable, scalable test suite — not just write individual tests. They want to know:

  • Do you separate concerns (page logic vs. test logic)?
  • Do you make tests readable to non-engineers?
  • Do you reduce duplication without over-abstracting?
  • Do you handle cross-cutting concerns (auth, data, reporting) cleanly?

Common Questions & Strong Answers

”How would you structure a Playwright test framework from scratch?”

Strong answer structure:

  1. Start with project goals (team size, app type, CI requirements)
  2. Define layers (page objects, fixtures, utilities, tests)
  3. Explain config strategy (environments, reporters)
  4. Describe CI integration
playwright-tests/
├── e2e/
│   ├── auth/          # auth flow tests
│   ├── checkout/      # checkout flow tests
│   └── smoke/         # cross-cutting smoke suite
├── pages/             # Page Object classes
├── fixtures/          # Custom Playwright fixtures
├── utils/             # Pure helpers (data factories, API clients)
├── playwright.config.ts
└── global-setup.ts    # storageState, DB seed

”What is the Page Object Model and when would you NOT use it?”

POM:

// pages/LoginPage.ts
export class LoginPage {
  constructor(private page: Page) {}

  async login(email: string, password: string) {
    await this.page.getByLabel('Email').fill(email);
    await this.page.getByLabel('Password').fill(password);
    await this.page.getByRole('button', { name: 'Sign in' }).click();
  }

  async getErrorMessage() {
    return this.page.getByRole('alert').textContent();
  }
}

When NOT to use POM:

  • One-off scripts or exploratory tests
  • Very simple apps with 2-3 pages
  • When the overhead of maintaining page classes exceeds the benefit
  • Teams that prefer a more functional/procedural style (use plain helpers instead)

“How do you share state between tests without coupling them?"

// ❌ Shared mutable state — order-dependent, fragile
let userId: string;
test.beforeAll(async () => { userId = await createUser(); });
test('uses userId', async ({ page }) => { /* depends on above */ });

// ✅ Fixture-based isolation — each test self-contained
const test = base.extend<{ user: User }>({
  user: async ({ request }, use) => {
    const user = await createUserViaApi(request);
    await use(user);
    await deleteUserViaApi(request, user.id); // teardown
  },
});

"How do you handle authentication across your test suite?”

// global-setup.ts: authenticate once, save storageState
async function globalSetup() {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER!);
  await page.getByLabel('Password').fill(process.env.TEST_PASS!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
  await browser.close();
}

// playwright.config.ts
export default defineConfig({
  use: { storageState: 'playwright/.auth/user.json' },
  globalSetup: './global-setup.ts',
});

Questions About Fixtures

”What’s the difference between a fixture and a helper function?”

FixtureHelper function
LifecycleSetup + teardown, scoped to testNo lifecycle management
DIInjected by Playwright automaticallyCalled explicitly
Scopetest / worker / fileN/A
Use forResources with cleanup (DB, auth, browser context)Pure transformations (format date, generate ID)

“When would you use worker scope vs test scope for a fixture?”

  • test scope: State that must be isolated per test (user session, created records)
  • worker scope: Expensive shared resources safe to reuse (DB connection pool, authenticated context for read-only tests)