{"id":49961199,"url":"https://github.com/balukov/e2e-best-practices","last_synced_at":"2026-05-18T02:27:15.987Z","repository":{"id":35612447,"uuid":"215223589","full_name":"balukov/e2e-best-practices","owner":"balukov","description":"The boilerplate project with advice best practices for e2e tests","archived":false,"fork":false,"pushed_at":"2026-04-08T02:09:29.000Z","size":554,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-04-08T04:12:08.292Z","etag":null,"topics":["best-practices","boilerplate","e2e","e2e-tests","husky","prettier","tslint"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/balukov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-10-15T06:29:03.000Z","updated_at":"2026-04-08T02:09:29.000Z","dependencies_parsed_at":"2023-01-16T01:03:55.973Z","dependency_job_id":null,"html_url":"https://github.com/balukov/e2e-best-practices","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/balukov/e2e-best-practices","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balukov%2Fe2e-best-practices","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balukov%2Fe2e-best-practices/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balukov%2Fe2e-best-practices/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balukov%2Fe2e-best-practices/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/balukov","download_url":"https://codeload.github.com/balukov/e2e-best-practices/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/balukov%2Fe2e-best-practices/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33162541,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-17T22:39:12.733Z","status":"online","status_checked_at":"2026-05-18T02:00:06.436Z","response_time":71,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["best-practices","boilerplate","e2e","e2e-tests","husky","prettier","tslint"],"created_at":"2026-05-18T02:27:15.021Z","updated_at":"2026-05-18T02:27:15.975Z","avatar_url":"https://github.com/balukov.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The boilerplate with advice best practices for e2e tests\n\n\u003e **Note (2026):** This guide was written in 2019 using WebdriverIO v5, TSLint, Mocha, and TypeScript 3.6.\n\u003e The **architectural concepts** (Page Object Model, typed tests, component structure, `data-test` attributes, test organization) remain best practices.\n\u003e The **tooling** is outdated — the modern stack is **Playwright + TypeScript 5 + ESLint + Prettier**.\n\u003e Each outdated section below is marked with a ⚠️ note.\n\n## 1. Introduction\n\nThis project is not just the instruction about the best practices or just the boilerplate with ready to use code.\nIt's an alliance for both.\nCode = advices, advices = code.\nThe code was written a total with this advice which contains specific examples from boilerplate.\n\nAll examples are hidden under text with arrow like this:\n\n \u003cdetails\u003e\n \u003csummary\u003eReal example (click for expand)\u003c/summary\u003e\n\n---\n\n```\nexample of the code\n```\n\nExplanation if necessary\n\n[Link to the real place in the file with this code]()\n\n---\n\n \u003c/details\u003e\n\nIf you don't understand the guide - look the code with full files.\nIf you don't understand how something works in the code - look this guide with the description and reasons why I did that.\n\nThe project is written using framework Webdriver.io for Node.js and Typescript.\nBut every advice can be used for other programming languages with adjustments.\n\nThe project uses the mock site https://www.saucedemo.com for demo tests.\nTo test your project you should modify the pages, components and, of course, tests.\n\nAll tests 100% work.\n\nFeel free to create issues with questions and additions.\n\n### 1.1. Installation\n\n```\nnpm i\n```\n\n### 1.2. Running tests\n\n```\nnpm test\n```\n\n## 2. Developers standards\n\nThe important thing that tests are a **project**. Not just a suite of tests which do some magic and talk \"green\" or \"red\". It's a real project with its architecture, rules of code, running, configuration. And for convenient maintenance, development, and teamwork you need to use the same rules as for the common project.\n\n### 🧩 Typings\n\nIf your language support typings it is necessary to use it - fewer bugs in developing, smart suggests. This project uses Typescript and typings uses everywhere.\n\n \u003cdetails\u003e\n \u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\ngetButton(productName: string): string {\n const productIndex = this.getProductIndex(productName);\n return this.productButtons()[productIndex].getText();\n}\n```\n\nFunction getButton() can take an only string and return only strings.\n\n[pages/components/list.page.ts](pages/components/list.page.ts#L34)\n\n---\n\n \u003c/details\u003e\n\n### 🧩 Static code analysis (Linter)\n\n\u003e ⚠️ **Outdated:** TSLint was deprecated in 2019. Use **ESLint** with `@typescript-eslint` instead. For the `.only()` rule, use `eslint-plugin-no-only-tests`.\n\nIt helps to avoid common language mistakes, make rules specifically for the project.\n\n \u003cdetails\u003e\n \u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```json\n \"mocha-avoid-only\": true\n```\n\nThe rule does not allow us to commit to the repository .only (). Because only tests with .only () will be run. And we want to run all.\n\n[tslint.json](tslint.json#L13)\n\n---\n\n \u003c/details\u003e\n\n### 🧩 Prettier\n\nPrettier makes code beautiful and consistently if many developers work with the project.\n\n### 🧩 Autorun\n\n\u003e ⚠️ **Outdated config:** Husky v9+ uses a `.husky/` directory with shell scripts instead of `package.json` hooks. The concept of pre-commit automation is still a best practice.\n\nHusky runs linter and prettier automatically before commit because without autorun everybody forgets runs.\n\n\u003c!-- Test suite -\u003e tests -\u003e pages -\u003e components -\u003e steps -\u003e selector actions -\u003e selectors --\u003e\n\n## 3. Folders structure\n\n### 🧩 Choose type of storing tests\n\nThere are two options for storing tests in files: app-pages and app-actions. The type of storing depends on the project. Think about the future when you choose. If you want to run tests particularly, which tests should be run? \"Profile page tests\" which do all checks for the profile page or \"Purchase tests\" which check all stages for buying. It's not strong definitions and your project can use both simultaneously, it's complicated but it's possible. I recommend starting from app-pages and if you understand this doesn't fit then to switch to app-actions.\n\n🔘 App-pages\n\nThe app divides into real pages, each file contains tests for the page where did the last action in the test. Simple to start, complicate continue.\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```\n.\n├── ...\n├── specs # Test files\n│ ├── index.spec # Tests for index page\n│ ├── products.spec # Tests for product page\n│ └── cart.spec # Tests for cart page\n└── ...\n```\n\n---\n\n\u003c/details\u003e\n\n🔘 App-actions\n\nThe app divides to user action parts, imagine how a user can use your app. Complicate to start, simple to continue.\n\n\u003cdetails\u003e\n\u003csummary\u003eExample\u003c/summary\u003e\n\n---\n\n```\n.\n├── ...\n├── specs # Test files\n│ ├── download.spec # Tests for downloads files\n│ ├── crud-goods.spec # Create-Read-Update-Delete for item\n│ └── purchase-cancel.spec # Cancelation actions\n└── ...\n```\n\n---\n\n\u003c/details\u003e\n\n## 4. Test structure\n\n### 🧩 Import pages what you need in the test at top file\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nimport index from '@pages/index.page';\nimport products from '@pages/products.page';\n```\n\n[specs/products.spec.ts](specs/products.spec.ts#L7)\n\n---\n\n\u003c/details\u003e\n\n### 🧩 One describe per file\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\ndescribe('Products page', () =\u003e {\n // tests\n}\n```\n\n[specs/products.spec.ts](specs/products.spec.ts#L12)\n\n---\n\n\u003c/details\u003e\n\n### 🧩 Use preconditions\n\nActions should be done before the test starts. Usually, these are: open app URL -\u003e authorization -\u003e open page for a test (if it is needed)\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nbefore('Open index page', () =\u003e {\n  browser.url('');\n  index.loginForm().authStandardUser();\n});\n```\n\n[specs/products.spec.ts](specs/products.spec.ts#L13)\n\n---\n\n\u003c/details\u003e\n\n### 🧩 Test step structure: page.component.step\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nindex.loginForm().authStandardUser();\n```\n\npage is \"index\"\ncomponent is \"loginForm\"\nstep is \"authStandardUser\"\n\n[specs/products.spec.ts](specs/products.spec.ts#L15)\n\n```typescript\nproducts.list().addToCart(productName);\n```\n\npage is \"products\"\ncomponent is \"list\"\nstep is \"addToCart\"\n\n[specs/products.spec.ts](specs/products.spec.ts#L19)\n\n---\n\n\u003c/details\u003e\n\n## 4. Page structure\n\n### 🧩 Import all components for page\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nimport list from '@components/list.page';\n```\n\n[pages/products.page.ts](pages/products.page.ts#L1)\n\n---\n\n\u003c/details\u003e\n\n## 5. Component structure\n\n### 🧩 All selectors begin with the component selector\n\nLet xPath for components and add the path to begin the every interact element\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nprivate list = () =\u003e $('//*[@class=\"inventory_list\"]');\n\nprivate productNames = () =\u003e this.list().$$(`//*[@class=\"inventory_item_name\"]`);\nprivate productButtons = () =\u003e this.list().$$(`//*[contains(@class,\"btn_inventory\")]`);\n```\n\n[pages/components/list.page.ts](pages/components/list.page.ts#L6)\n\n---\n\n\u003c/details\u003e\n\n### 🧩 Component file contains steps, selector actions, selectors\n\n## 6. Selector structure\n\n### 🧩 Use XPath\n\n\u003e ⚠️ **Outdated:** XPath is now a last resort. Modern frameworks provide semantic locators — Playwright offers `getByRole()`, `getByText()`, `getByTestId()` which are far more readable and resilient. Prefer CSS selectors or framework-native locators.\n\n### 🧩 Set asterisk for selectors\n\n\u003e ⚠️ **Outdated:** The `//*[@class=\"...\"]` pattern is brittle with class name changes. Prefer `data-testid` attributes with framework-native locators like `page.getByTestId('inventory-list')`.\n\nIt helps if the app will be refactored\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```typescript\nprivate list = () =\u003e $('//*[@class=\"inventory_list\"]');\n```\n\n[pages/components/list.page.ts](pages/components/list.page.ts#L6)\n\n---\n\n\u003c/details\u003e\n\n### 🧩 Add \"data-test\" for elements where it's possible.\n\nAsk developers or do it yourself. It makes the call selectors easier.\n\n\u003cdetails\u003e\n\u003csummary\u003eReal example\u003c/summary\u003e\n\n---\n\n```html\n\u003c!-- element on page --\u003e\n\u003cinput type=\"text\" class=\"form_input\" data-test=\"username\" id=\"user-name\" placeholder=\"Username\" value=\"\" /\u003e\n```\n\n```typescript\n// selector\nprivate username = () =\u003e $('//*[@data-test=\"username\"]');\n```\n\n[pages/components/login-form.page.ts](pages/components/login-form.page.ts#L6)\n\n---\n\n\u003c/details\u003e\n\n## 7. Modern Stack (2026)\n\nIf starting a new E2E project today, consider this stack:\n\n- **Playwright** — Fast, reliable, auto-waiting, built-in test runner, trace viewer, codegen\n- **TypeScript 5** — Strict typing with modern features\n- **ESLint** + **Prettier** — Linting and formatting\n- **Husky v9** + **lint-staged** — Pre-commit automation\n- **`data-testid` attributes** — Stable selectors (unchanged from this guide)\n- **Page Object Model** — Still the recommended pattern (unchanged from this guide)\n\n### Key features available in modern frameworks that this guide doesn't cover\n\n- **Auto-waiting** — No more explicit waits or sleep\n- **API mocking** — `page.route()` to intercept network requests\n- **Visual regression** — Built-in screenshot comparison\n- **Accessibility testing** — `@axe-core/playwright`\n- **Parallel execution** — Isolated browser contexts by default\n- **Trace \u0026 video recording** — Built-in debugging tools\n\n## Coming soon\n\n\u003e ⚠️ **Update:** Most of these features are now built into modern frameworks like Playwright:\n\u003e - ~~Accessibility tests~~ → `@axe-core/playwright`\n\u003e - ~~Visual comparison tests~~ → `expect(page).toHaveScreenshot()`\n\u003e - ~~Docker container for CI~~ → Playwright provides official Docker images\n\u003e - ~~Clear and readable report~~ → Playwright HTML Reporter, Trace Viewer\n\n- Pictures for visualizing the structure\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbalukov%2Fe2e-best-practices","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbalukov%2Fe2e-best-practices","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbalukov%2Fe2e-best-practices/lists"}