UNPKG

codeceptjs

Version:

Supercharged End 2 End Testing Framework for NodeJS

324 lines (234 loc) 10.6 kB
--- permalink: /tutorial title: CodeceptJS Complete Tutorial --- # Tutorial: Testing a Checkout Page **[CodeceptJS](https://codecept.io) is a popular open-source end-to-end testing framework** for JavaScript. It is designed to make web tests readable and easy to maintain by writing them as a linear scenario of user actions. By default it drives the browser with **[Playwright](https://playwright.dev)**, but the same tests can run via WebDriver, Puppeteer, or Appium without changes. In this tutorial we write a real, runnable test for the **[Bootstrap checkout example](https://getbootstrap.com/docs/4.0/examples/checkout/)** — a public page with a billing and payment form. By the end you will have a clean test and a reusable page object. ## Install CodeceptJS You need Node.js (and npm) installed. Check with: ```bash node --version npm --version ``` Create a new folder, then install CodeceptJS together with Playwright: ```bash npm init -y npm install codeceptjs playwright --save-dev npx playwright install --with-deps ``` `npx playwright install` downloads the Chromium, Firefox, and WebKit browsers; `--with-deps` also installs the system libraries they need. Now scaffold the project: ```bash npx codeceptjs init ``` `init` runs a short wizard. Accept the defaults — when asked for the **base URL** enter `https://getbootstrap.com`, and name the first test **Checkout**. This creates: ``` . ├── codecept.conf.js ├── package.json └── Checkout_test.js ``` `codecept.conf.js` holds the project configuration. Because CodeceptJS 4.x uses **ES modules**, the config and tests use `import`/`export` syntax — `init` sets `"type": "module"` in `package.json` for you. Open `codecept.conf.js`. The two settings that matter here are the helper and the base URL: ```js import { setHeadlessWhen } from '@codeceptjs/configure' // show the browser locally, run headless on CI setHeadlessWhen(process.env.CI) export const config = { tests: './*_test.js', output: './output', helpers: { Playwright: { url: 'https://getbootstrap.com', browser: 'chromium', }, }, } ``` ## Your First Test Open `Checkout_test.js`: ```js Feature('Checkout'); Scenario('test something', ({ I }) => { }); ``` A test lives inside a `Scenario` block. Let's open the checkout page: ```js Feature('Checkout'); Scenario('test something', ({ I }) => { I.amOnPage('/docs/4.0/examples/checkout/'); }); ``` `I.amOnPage()` navigates the browser. Because the path is relative, it is appended to the base URL from the config — keep the base URL in config so you can switch between staging and production without touching tests. But you may be wondering... ### What is `I`? In CodeceptJS the `I` object is the **actor** — it represents the user performing actions. It exposes methods (called *actions*) that simulate interactions with the app: - `I.amOnPage(url)` — navigate to a URL - `I.click(locator)` — click an element - `I.fillField(field, value)` — type into an input - `I.selectOption(select, option)` — choose an option in a dropdown - `I.checkOption(locator)` — tick a checkbox or radio - `I.see(text)` — assert that text is visible - `I.seeInField(field, value)` — assert an input holds a value CodeceptJS **waits automatically** before clicking, filling, and most other actions, so you rarely need explicit waits. Steps also write themselves into a promise chain, so you usually **don't need `await`** for regular actions — only for `grab*` actions and page object methods that return data. ### Locating Elements Most actions accept a locator. CodeceptJS supports several strategies — prefer the readable ones: ```js // by visible text / label I.click('Continue to checkout'); I.fillField('First name', 'John'); // by ARIA role and accessible name (resilient to CSS changes) I.click({ role: 'button', name: 'Continue to checkout' }); // by CSS or XPath, when nothing semantic is available I.fillField('#email', 'john@example.com'); ``` > **Best practice:** prefer labels and ARIA locators (`{ role, name }`). They survive styling changes and document intent. Fall back to CSS/XPath only when needed. ## Writing the Checkout Test The Bootstrap checkout form has billing fields, country/state selects, and a payment section. CodeceptJS finds inputs by their visible `<label>`, so the test reads like the form: ```js Feature('Checkout'); Scenario('fill in the checkout form', ({ I }) => { I.amOnPage('/docs/4.0/examples/checkout/'); I.see('Checkout form'); // billing address — fields located by their labels I.fillField('First name', 'John'); I.fillField('Last name', 'Doe'); I.fillField('Username', 'johndoe'); I.fillField('#email', 'john@example.com'); // label has "(Optional)", use CSS I.fillField('Address', '123 Main St.'); I.selectOption('Country', 'United States'); I.selectOption('State', 'California'); I.fillField('Zip', '10001'); // shipping / preferences I.checkOption('Shipping address is the same as my billing address'); I.checkOption('Save this information for next time'); // payment — "Credit card" is selected by default I.click('Credit card'); I.fillField('Name on card', 'John Doe'); I.fillField('Credit card number', secret('4111 1111 1111 1111')); // verify the form holds what we entered I.seeInField('First name', 'John'); I.seeInField('Address', '123 Main St.'); I.click('Continue to checkout'); }); ``` A few things worth noting: - **`secret()`** wraps the card number so it is masked (`****`) in logs and reports. Use it for any sensitive value — see [Secrets](/secrets). - Never use a real card number. Payment providers like Stripe publish [test card numbers](https://docs.stripe.com/testing) for exactly this. - This is a static demo page with no backend, so we verify by reading field values back with `I.seeInField`. On a real shop you would assert a confirmation, e.g. `I.see('Your order has been placed')`. ### A Negative Scenario Good test suites cover failures too. The form validates on submit — submitting it empty shows error messages. CodeceptJS doesn't allow multiple scenarios in one file's suite to nest, but you can add as many `Scenario` blocks as you like: ```js Scenario('shows validation errors on empty submit', ({ I }) => { I.amOnPage('/docs/4.0/examples/checkout/'); I.click('Continue to checkout'); I.see('Valid first name is required.'); }); ``` ### Running the Test ```bash npx codeceptjs run --steps ``` `--steps` prints every step as it runs. Useful flags while developing: - `--steps` — print each step - `--debug` — steps plus extra debug output (recommended while writing tests) - `--verbose` — everything, including the promise chain Set a breakpoint to inspect the page interactively by adding `pause()` to the scenario: ```js Scenario('fill in the checkout form', ({ I }) => { I.amOnPage('/docs/4.0/examples/checkout/'); I.fillField('First name', 'John'); pause(); // test stops here; type steps live in the browser }); ``` In the pause shell you can type `I.click('...')`, inspect the page, and find better locators. See [Debugging](/debugging). The browser is shown locally and runs headless on CI thanks to `setHeadlessWhen(process.env.CI)`. To force it either way for one run: ```bash npx codeceptjs run --headless ``` Once the test is stable, run the whole suite: ```bash npx codeceptjs run ``` ## Refactoring with a Page Object What if more tests need to fill this form? Copy-pasting steps doesn't scale. The **Page Object** pattern keeps locators and interactions in one reusable place. Generate one: ```bash npx codeceptjs gpo ``` Call it `Checkout`. It is created in `./pages/Checkout.js` and registered in `codecept.conf.js` under `include`: ```js export const config = { // ... include: { checkoutPage: './pages/Checkout.js', }, } ``` Page objects are **classes**. Move the form interactions into named methods: ```js const { I } = inject(); class CheckoutPage { url = '/docs/4.0/examples/checkout/' open() { I.amOnPage(this.url); I.see('Checkout form'); } fillBillingAddress({ firstName, lastName, username, address, country, state, zip }) { I.fillField('First name', firstName); I.fillField('Last name', lastName); I.fillField('Username', username); I.fillField('Address', address); I.selectOption('Country', country); I.selectOption('State', state); I.fillField('Zip', zip); } payWithCard(name, number) { I.click('Credit card'); I.fillField('Name on card', name); I.fillField('Credit card number', secret(number)); } submit() { I.click('Continue to checkout'); } } export default CheckoutPage ``` > `inject()` returns a lazy proxy, so it's safe to destructure `I` before the class. Export the **class** — CodeceptJS auto-instantiates it. (Plain-object page objects still work but classes support lifecycle hooks and inheritance.) The test now reads at the business level. Inject `checkoutPage` by the name you set in the config: ```js Feature('Checkout'); Scenario('complete a checkout', ({ I, checkoutPage }) => { checkoutPage.open(); checkoutPage.fillBillingAddress({ firstName: 'John', lastName: 'Doe', username: 'johndoe', address: '123 Main St.', country: 'United States', state: 'California', zip: '10001', }); checkoutPage.payWithCard('John Doe', '4111 1111 1111 1111'); checkoutPage.submit(); I.seeInField('First name', 'John'); }); ``` Shorter, intention-revealing, and every other checkout test can reuse the same methods. As coverage grows, add methods to the page object instead of duplicating steps. ## Going Further When you have many tests, run them in parallel using Node workers: ```bash npx codeceptjs run-workers 3 ``` From here, explore: - [Locators](/locators) — every locating strategy in depth - [Page Objects](/pageobjects) — fragments, step objects, lifecycle hooks - [Data-driven tests](/data) — run one scenario over many inputs - [Debugging](/debugging) — `pause()`, the interactive shell, and AI-assisted debugging - [Continuous Integration](/continuous-integration) — running the suite on CI ## Summary If you are just starting with test automation, CodeceptJS lets you describe tests in near-natural language and handles waiting and retries for you. If you already know JavaScript, page objects and dependency injection keep your suite focused on business behavior — which is what keeps tests stable and maintainable as the app grows. > [▶ Next: CodeceptJS Basics](/basics/) </content> </invoke>