Playwright. Parameters & Requests

Agenda

  • Fixtures. Again
  • Parameters
  • Routing

Fixtures. Again

  • merging
  • options
  • scopes
  • best practicies

Merge fixtures

// composite.fixture.ts
import { mergeTests } from '@playwright/test';
import { test as dbTest } from 'database-test-utils';
import { test as a11yTest } from 'a11y-test-utils';

export const test = mergeTests(dbTest, a11yTest);
// composite.fixture.ts
import { test as baseTest } from '@playwright/test';

type Context = {
  usePassword(length: number): string
}

export const test = baseTest.extend({
  usePassword: async ({}, use) => {
    await use((length) => {
      return randomPassowrd(length)
    })
  },
});

// test
test('some test', async ({usePassword}) => {
  const user = {
    email: 'some@example.com',
    password: await usePassword(20),
  }
})

Options

import { test as base } from '@playwright/test';

export const test = base.extend({
  helperFixture: [async ({}, use) => {
    // ...
  }, { box: true, title: 'Custom title', timeout: 60000 }],
});
import { test as base } from '@playwright/test';

const test = base.extend({
  workerFixture: [async ({ browser }) => {
    // workerFixture setup...
    await use('workerFixture');
    // workerFixture teardown...
  }, { scope: 'worker' }],

  autoWorkerFixture: [async ({ browser }) => {
    // autoWorkerFixture setup...
    await use('autoWorkerFixture');
    // autoWorkerFixture teardown...
  }, { scope: 'worker', auto: true }],

  testFixture: [async ({ page, workerFixture }) => {
    // testFixture setup...
    await use('testFixture');
    // testFixture teardown...
  }, { scope: 'test' }],

  autoTestFixture: [async () => {
    // autoTestFixture setup...
    await use('autoTestFixture');
    // autoTestFixture teardown...
  }, { scope: 'test', auto: true }],
});
test.beforeAll(async () => { /* ... */ });
test.beforeEach(async ({ page }) => { /* ... */ });
test('first test', async ({ page }) => { /* ... */ });
test('second test', async ({ testFixture }) => { /* ... */ });
test.afterEach(async () => { /* ... */ });
test.afterAll(async () => { /* ... */ });

Fixtures

  1. + "auto + worker" - самые первые
  2. + "worker" - до теста
  3. + "auto+test" - перед beforeEach, после after Each (default)
  4. -"worker" - создается перед тестом, удаляется в конце (перед auto+worker)
  5. -"auto+test"
  6. -"worker" если нет других тестов
  7. - "auto+worker"

Parameters

const PARAMS = [
  {name: 'qwe'},
  {name: 'asd'},
]

for (const param of PARAMS){
  test(`test case: ${param.name}`, async ({page}) => {
    page.goto('/')
  })
}

Parameters

import fs from 'node:fs';
import path from 'node:path';
import { test } from '@playwright/test';

import { parse } from 'csv-parse/sync';

const records = parse(fs.readFileSync(path.join(__dirname, 'input.csv')), {
  columns: true,
  skip_empty_lines: true
});

// "test_case","some_value","some_other_value"
// "value 1","value 11","foobar1"

for (const record of records) {
  test(`foo: ${record.test_case}`, async ({ page }) => {
    console.log(record.test_case, record.some_value, record.some_other_value);
  });
}

Parameters. Best Practicies

  • статическая информация (фаил, константа)
  • нет внешних зависимостей (.env)
  • повторяемый результат
  • boundary values & equivalent classes

Routing

  • HTTP Requests
  • Proxy
  • Extra headers

Routing. Extra headers

import { defineConfig } from '@playwright/test';
export default defineConfig({
  use: {
    baseURL: 'https://api.github.com',
    extraHTTPHeaders: {
      'Accept': 'application/vnd.github.v3+json',
      'Authorization': `token ${process.env.API_TOKEN}`,
    },
  }
});

Routing. Proxy

import { defineConfig } from '@playwright/test';
export default defineConfig({
  use: {
    proxy: {
      server: 'http://my-proxy:8080',
      username: 'user',
      password: 'secret'
    },
  }
});

Routing. HTTP Requests

test.beforeAll(async ({ request }) => {
  // Create a new repository
  const response = await request.post('/user/repos', {
    data: {
      name: 'My repo'
    }
  });
  expect(response.ok()).toBeTruthy();
});

test.afterAll(async ({ request }) => {
  // Delete the repository
  const response = await request.delete(`/repos/${USER}/${REPO}`);
  expect(response.ok()).toBeTruthy();
});

Routing. Notes

  • Creates a browser (even for requests only)
  • Extra headers can be overwritten by parent
  • Can manipulate with req/res

Routing. Manupulate

test('context request will share cookie storage with its browser context', 
 async ({
  page,
  context,
 }) => {
    const response = await context.request.fetch(route.request());
    await route.fulfill({
      response, // prev response
      headers: { ...response.headers(), foo: 'bar' },
    });
  // OR
  await page.route('**/v1/**', route => route.fulfill({ path: 'mock_data.json' }));
})

Auth State. Setup

import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  await page.goto('https://example.com/login');
  await page.getByLabel('Username or email address').fill('username');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('https://github.com/');
  await expect(page.getByRole('button', { name: 'View profile and more' })).toBeVisible();
  // End of authentication steps.
  await page.context().storageState({ path: authFile });
});

Auth State. Setup

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    // Setup project
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Firefox'],
        // Use prepared auth state.
        storageState: 'playwright/.auth/user.json',
      },
      dependencies: ['setup'],
    },
  ],
});

Best practicies

  • routes - для браузеров
  • auth state - штука ситуативная

References

pw. Parameters & Requests

By vitalic gorodkov

pw. Parameters & Requests

  • 193