diff --git a/docs/src/test-fixtures-js.md b/docs/src/test-fixtures-js.md
index 8d2da6ea2e..85c8a3fe4e 100644
--- a/docs/src/test-fixtures-js.md
+++ b/docs/src/test-fixtures-js.md
@@ -35,7 +35,93 @@ Here is a list of the pre-defined fixtures that you are likely to use most of th
Here is how typical test environment setup differs between traditional test style and the fixture-based one.
-We assume a `TodoPage` class that helps interacting with a "todo list" page of the web app, following the [Page Object Model](./pom.md) pattern. It uses Playwright's `page` internally.
+`TodoPage` is a class that helps interacting with a "todo list" page of the web app, following the [Page Object Model](./pom.md) pattern. It uses Playwright's `page` internally.
+
+
+ Click to expand the code for the TodoPage
+
+
+```js tab=js-js
+// todo-page.js
+export class TodoPage {
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor(page) {
+ this.page = page;
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async addToDo(text) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async remove(text) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+```js tab=js-ts
+// todo-page.ts
+import { Page, Locator } from '@playwright/test';
+
+export class TodoPage {
+ private readonly inputBox: Locator;
+ private readonly todoItems: Locator;
+
+ constructor(public readonly page: Page) {
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ async addToDo(text: string) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ async remove(text: string) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+
+
```js
// todo.spec.js
@@ -78,6 +164,91 @@ Fixtures have a number of advantages over before/after hooks:
- Fixtures are **flexible**. Tests can use any combinations of the fixtures to tailor precise environment they need, without affecting other tests.
- Fixtures simplify **grouping**. You no longer need to wrap tests in `describe`s that set up environment, and are free to group your tests by their meaning instead.
+
+ Click to expand the code for the TodoPage
+
+
+```js tab=js-js
+// todo-page.js
+export class TodoPage {
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor(page) {
+ this.page = page;
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async addToDo(text) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async remove(text) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+```js tab=js-ts
+// todo-page.ts
+import { Page, Locator } from '@playwright/test';
+
+export class TodoPage {
+ private readonly inputBox: Locator;
+ private readonly todoItems: Locator;
+
+ constructor(public readonly page: Page) {
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ async addToDo(text: string) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ async remove(text: string) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+
+
```js tab=js-js
// todo.spec.js
const base = require('@playwright/test');
@@ -140,6 +311,123 @@ To create your own fixture, use [`method: Test.extend`] to create a new `test` o
Below we create two fixtures `todoPage` and `settingsPage` that follow the [Page Object Model](./pom.md) pattern.
+
+ Click to expand the code for the TodoPage and SettingsPage
+
+```js tab=js-js
+// todo-page.js
+export class TodoPage {
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor(page) {
+ this.page = page;
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async addToDo(text) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async remove(text) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+```js tab=js-ts
+// todo-page.ts
+import { Page, Locator } from '@playwright/test';
+
+export class TodoPage {
+ private readonly inputBox: Locator;
+ private readonly todoItems: Locator;
+
+ constructor(public readonly page: Page) {
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ async addToDo(text: string) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ async remove(text: string) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+SettingsPage is similar:
+
+```js tab=js-js
+// settings-page.js
+export class SettingsPage {
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor(page) {
+ this.page = page;
+ }
+
+ async switchToDarkMode() {
+ // ...
+ }
+}
+```
+
+```js tab=js-ts
+// settings-page.ts
+import { Page } from '@playwright/test';
+
+export class SettingsPage {
+ constructor(public readonly page: Page) {
+ }
+
+ async switchToDarkMode() {
+ // ...
+ }
+}
+```
+
+
+
+
```js tab=js-js
// my-test.js
const base = require('@playwright/test');
@@ -476,6 +764,91 @@ Playwright Test supports running multiple test projects that can be separately c
Below we'll create a `defaultItem` option in addition to the `todoPage` fixture from other examples. This option will be set in configuration file. Note the tuple syntax and `{ option: true }` argument.
+
+ Click to expand the code for the TodoPage
+
+
+```js tab=js-js
+// todo-page.js
+export class TodoPage {
+ /**
+ * @param {import('@playwright/test').Page} page
+ */
+ constructor(page) {
+ this.page = page;
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async addToDo(text) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ /**
+ * @param {string} text
+ */
+ async remove(text) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+```js tab=js-ts
+// todo-page.ts
+import { Page, Locator } from '@playwright/test';
+
+export class TodoPage {
+ private readonly inputBox: Locator;
+ private readonly todoItems: Locator;
+
+ constructor(public readonly page: Page) {
+ this.inputBox = this.page.locator('input.new-todo');
+ this.todoItems = this.page.getByTestId('todo-item');
+ }
+
+ async goto() {
+ await this.page.goto('https://demo.playwright.dev/todomvc/');
+ }
+
+ async addToDo(text: string) {
+ await this.inputBox.fill(text);
+ await this.inputBox.press('Enter');
+ }
+
+ async remove(text: string) {
+ const todo = this.todoItems.filter({ hasText: text });
+ await todo.hover();
+ await todo.getByLabel('Delete').click();
+ }
+
+ async removeAll() {
+ while ((await this.todoItems.count()) > 0) {
+ await this.todoItems.first().hover();
+ await this.todoItems.getByLabel('Delete').first().click();
+ }
+ }
+}
+```
+
+
+
```js tab=js-js
// my-test.js