E2E testing with jest-puppeteer

E2E testing with jest-puppeteer

by Zahid Hasan

1/27/2025

E2E testing with jest-puppeteer

Front-end testing ensures that everything on the user interface works as expected and looks visually correct. It focuses on testing the front-end code’s visual components, user interactions, and responsiveness. Different types of front-end testing—such as unit testing, integration testing, and end-to-end (E2E) testing—address various aspects of quality and reliability. Tools like Testsigma, Jest, Cypress, and Selenium help automate these tests, enabling developers to catch bugs early in the development process and ensure a seamless user experience.

E2E Testing: In End-to-End Testing, actual user behaviors and interactions with the application are replicated. This testing approach provides a realistic assessment of the system’s functionality and ensures that the complete flow of the application is verified from start to finish. The importance of end-to-end testing lies in its ability to identify issues that arise when different parts of the application interact, ensuring that all processes work seamlessly as intended.

We utilize two primary tools: Jest for robust testing frameworks and Puppeteer for simulating user interactions within the browser.

 

Jest: Jest is a widely-used JavaScript testing framework known for its ease of use and powerful API for writing tests and assertions. It is particularly effective for both unit and integration testing. To get started or learn more, visit the Jest documentation.

 

Puppeteer: Puppeteer is a Node.js library developed by Google that enables control over headless Chrome or Chromium. It serves as a powerful browser automation tool, allowing for comprehensive interaction with web pages without a physical interface. This makes Puppeteer an ideal choice for automating tasks and backend processes. To learn more, visit the Puppeteer documentation.

 

Jest-Puppeteer: jest-puppeteer is a Jest preset that enables end-to-end testing with Puppeteer. It offers a straightforward API for launching new browser instances and interacting with web pages through them. For more information, visit  jest-puppeteer 

 

Setup and Configuration: To install Jest and Puppeteer, open the command line in your project directory and run:

 

For npm users:

npm install --save-dev jest puppeteer jest-puppeteer

For yarn users:

yarn add --dev jest puppeteer jest-puppeteer

Configure jest

Next, you need to configure Jest to work seamlessly with Puppeteer

Create a Jest configuration file in your project's root directory if you don't already have one. In this configuration file, specify jest-puppeteer as a preset:

// jest-integration.config.js
module.exports = {
  preset: 'jest-puppeteer',
  rootDir: 'integration',
  testRegex: './*\\.test\\.(m?js|tsx?)$',
  setupFilesAfterEnv: ['<rootDir>/setup-jest.js'],
}

This is the Jest configuration for running your integration tests. It specifies the preset to use (jest-puppeteer), which integrates Jest with Puppeteer. It also defines the root directory for your tests, a regex pattern for test files, and a setup file (setup-jest.js) to configure the testing environment before the tests are run.

// jest-puppeteer.config.js


const fs = require('fs');


fs.mkdirSync(`screenshots`, { recursive: true });
fs.mkdirSync(`recordings`, { recursive: true });
fs.mkdirSync(`logs`, { recursive: true });


const GITHUB_ACTION_RUNNER = process.env.GITHUB_ACTION_RUNNER === "true";
const CI = process.env.CI === "true";
const LAUNCH_SERVER = process.env.LAUNCH_SERVER === "true";


console.log(`GITHUB_ACTION_RUNNER: ${GITHUB_ACTION_RUNNER}`);
console.log(`CI: ${CI}`);
console.log(`LAUNCH_SERVER: ${LAUNCH_SERVER}`);


const getLaunchOptions = () => {
  if (GITHUB_ACTION_RUNNER || CI) {
    return {
      dumpio: true,
      headless: true,
      ignoreDefaultArgs: ["--disable-extensions"],
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-accelerated-2d-canvas',
        '--disable-gpu',
        '--disable-dev-shm-usage'
      ],
    };
  }
  return {
    dumpio: true,
    headless: false,
    ignoreDefaultArgs: [],
    args: [],
       slowMo: 25,
  };
};
const getServerOptions = () => {
  if (LAUNCH_SERVER) {
    return {
      server: {
        command: `yarn serve --port 9999`,
        host: `localhost`,
        port: 9999,
        protocol: `http`,
        launchTimeout: 15000,
        debug: true,
        usedPortAction: "kill",
        waitOnScheme: {
          delay: 1000,
        },
      },
    };
  }
  return {};
};
module.exports = {
  launch: getLaunchOptions(),
  browserContext: 'default',
  ...getServerOptions(),
};

This is the configuration for Puppeteer when used with Jest. It defines how Puppeteer launches and interacts with the browser in the context of your tests.

It includes settings for things like headless mode, launch arguments for Puppeteer, and server settings.

It handles environment-specific options for running tests in CI/CD pipelines (e.g., on GitHub Actions) and local development (e.g., enabling or disabling headless mode, slow motion for debugging).

Additionally, it ensures necessary directories (screenshots, recordings, logs) are created.

Shortly, jest-puppeteer.config.js is used for configuring how Puppeteer interacts with Jest, while jest-integration.config.js is a Jest-specific configuration that sets up the overall testing environment for your integration tests.

This Jest test (checkout.test.js) is designed to automate and test a checkout flow for an EarningsCall API platform.  It’s critical that this piece of code works every time a new code change is introduced, because it’s tied to how we make money on the EarningsCall platform.  Here's a breakdown of the key sections of the code:

1. Environment Variables and Helper Imports

const endpoint = process.env.ENDPOINT || "http://localhost:8000";
const { performLogin, logoutUser } = require("./helper");
const { sleep } = require("./utils.js");

  • endpoint: Retrieves the endpoint URL from the environment variables (process.env.ENDPOINT), defaulting to http://localhost:8000 if not set.
  • performLogin and logoutUser: These are utility functions (imported from helper.js) that handle user login and logout actions in the test.
  • sleep:  A utility function (imported from utils.js) to pause execution for a specified amount of time, useful for waiting between actions.

2. API Call Functions

const makeApiCall = async ({ path, params }) => {...};
const makeApiCallWithRetries = async ({
  path,
  params,
  maxWaitTime = 75_000}) => {...};

  • makeApiCall: This function constructs a URL for an API endpoint, appends query parameters (including a cache buster to prevent caching), and performs a fetch request to get the API response. If the response is successful (response.ok), it returns the JSON content. Otherwise, it throws an error.
  • makeApiCallWithRetries: This is a wrapper around makeApiCall that will retry the API call in case of failure (e.g., network issues). It retries the call every 5 seconds until a maximum wait time (maxWaitTime, defaulting to 75 seconds) is exceeded. If the call is not successful within this time, it throws a timeout error.

 

3. Jest Test Suite Setup

describe("EarningsCall Checkout Flows", () => {...}, 15_000);

  • describe: This defines a test suite named "EarningsCall Checkout Flows." Inside this block, you define all the tests and setup/teardown logic related to the checkout flow.

4. Test Setup (beforeAll and beforeEach)

  beforeAll(async () => {
    console.log("Running beforeAll", endpoint);
    await page.goto(endpoint);
    await page.setViewport({ width: 1080, height: 1024 }); // Set screen size
  }
beforeEach(async () => {
    if (process.env.TEST_STAGE === "prod") {
      throw new Error("Don't run this test against prod.");
    }
  });

  • beforeAll: This setup function runs once before all tests in the suite. It navigates to the page (await page.goto(endpoint)) and sets the viewport size. The 15_000 argument sets a 15-second timeout for the beforeAll hook.
  • beforeEach: This runs before each individual test. It ensures that the test doesn't run in a production environment (process.env.TEST_STAGE === "prod"). If the environment is "prod", it throws an error and stops the test.

 

5. Test Case: "should successfully process payment info and generate API key"

it("should successfully process payment info and generate API key", async () => { ... });

  1.  Navigate and Verify

    The test starts by verifying that the initial page loads with specific text and title content.

    await expect(page).toMatchTextContent(
          "All Your Earnings Calls in One Place"
        );
        await expect(page.title()).resolves.toMatch(
          "Earnings call app for public companies and stocks."
        );
    
  • Explanation: Checks that the page displays the text “All Your Earnings Calls in One Place” and that the page title matches the string “Earnings call app for public companies and stocks.”

  1.  Login and Pricing Interaction

    Logs out the current user, logs in again, and navigates to the "Pricing" section to choose “API Pricing”

    await logoutUser(page);
        await performLogin(page);
        await expect(page).toMatchTextContent(
          "All Your Earnings Calls in One Place",
          { timeout: 15_000 }
        );
        await expect(page).toClick("button", { text: "Pricing" });
    
    
        await expect(page).toClick("span", { text: "API Pricing" });
        await expect(page).toMatchTextContent(
          "Elevate Your Data Access with Our Earnings Call API",
          { timeout: 15_000 }
        );
    

  2. Explanation:
    1. Logs out using logoutUser(page) and logs back in using performLogin(page).

performLogin: The performLogin function automates the login process in a test environment using a temporary email service provided by MailJS. For more details, you can refer to the Mail.tm documentation or visit their official website at Mail.tm.

Create a Temporary Email Account:

  const account = await mailjs.createOneAccount();
  const username = account.data.username;
  const password = account.data.password;

  • A temporary email account is created using MailJS. This service provides a unique email address (username) and password for testing purposes.
  • MailJS is the service provider that allows creating disposable email accounts, ideal for automated testing without using real credentials.

    Pass Test Data to the Backend:

      await integTestData({
        data: {
          username,
          password,
          email: username,
        },
      });
      
  • The generated email and password are passed to an integTestData function, which likely communicates with the backend to prepare the test environment (e.g., pre-registering the user for testing purposes).

    Navigate to the Login Page:

     await page.goto(`${endpoint}/login`);
      await page.setViewport({ width: 1080, height: 1024 });
    
  • Puppeteer navigates to the login page of the app (endpoint/login) and sets the viewport size for consistent visual testing.

                        Verify Page Content:

    await expect(page).toMatchTextContent("Welcome back", { timeout: 20_000 });
      await expect(page).toMatchTextContent("Please login to continue");
      await expect(page.title()).resolves.toMatch('EarningsCall · Login');

  • Assertions confirm that the page has loaded correctly by checking for specific text ("Welcome back" and "Please login to continue") and verifying the page title.

          Enter Credentials and Submit:

console.log("User name",username);
  await page.click('input[id="emailAddressField"]');
  await page.type('input[id="emailAddressField"]', username);
  await expect(page).toClick("button", { text: "Login With Email" })

  • The function interacts with the login form:
    • Click the email field.
    • Types the temporary email (username).
    • Click the "Login With Email" button to proceed.                

Verify Confirmation Messages:

await expect(page).toMatchTextContent("Confirm Your Email", { timeout: 20_000 });
  await expect(page).toMatchTextContent(`An Verification Code was sent to: ${username}`);
  await expect(page).toMatchTextContent(`Please check your email For Verification Code`);
  await expect(page).toMatchTextContent(`Didn’t Get Verification Code?`);

  • Verifies the presence of various confirmation messages that guide the user to check their email for a verification code.        

               Access the Temporary Email Inbox:

    await mailjs.login(username, password);
      const messages = await getMessages();

  • Logs in to the temporary email account and retrieves the messages in the inbox.

                Validate the Email Content:

    await expect(messages.length).toBeGreaterThan(0);
      await expect(messages[0].subject).toMatch(/Verify Your Account with This Code/);
      const verificationCode = messages[0].subject.match(/(\d{5})/)[0];

  • Checks that at least one email is received.
  • Confirms that the email subject matches the expected format ("Verify Your Account with This Code").
  • Extracts the verification code (a 5-digit number) from the email subject.

              Submit the Verification Code:

     await expect(page).toFill('input[class="vi"]', verificationCode);
    

  • Fills the verification code into the input field to complete the login process.

 

                      Return Credentials:

 return { username, password };

  • Returns the generated email and password, which can be used for further testing.

 

This code demonstrates a reliable way to test the login flow of an application in an automated environment. By using a temporary email service (MailJS), it simulates real-world user interactions, including receiving and entering a verification code.

MailJSMailJS is a service that provides disposable or temporary email accounts for developers and testers. It allows programmatic interaction with email inboxes via its API, making it an excellent tool for automating workflows that involve email-based verification, such as testing login processes, account creation, or other email-based features. For more information or to start using a similar service, visit mail.tm

Key Features of MailJS

  1. Temporary Email Accounts:
    • Automatically generates unique email addresses on demand.
    • These accounts are disposable and used primarily for testing purposes.
  2. API Access:
    • Offers a programmatic way to interact with the service, such as creating email accounts, retrieving emails, and parsing message content.
  3. Email Inbox Simulation:
    • Provides access to an inbox associated with the temporary email address.
    • Emails sent to the address can be fetched, read, and processed via the API.
  4. Email Parsing:
    • Enables extracting information such as the subject line, body text, and attachments from received emails.
    • Useful for retrieving verification codes, links, or other key details during tests.
  5. Real-Time Messaging:
    • New emails are available almost instantly in the temporary inbox, allowing for quick validation in automated workflows.

Why Use MailJS?

  • End-to-End Testing: MailJS simplifies testing of flows that involve email verification (e.g., login, sign-ups, password resets) without using real email accounts.
  • Avoid Email Clutter: Since the accounts are disposable, they avoid polluting real inboxes with test emails.
  • Data Privacy: MailJS ensures that your test data remains secure and isolated within the temporary account lifecycle.
  • Automation-Friendly: Its API is designed to integrate seamlessly with automated test frameworks, such as Puppeteer and Jest.

Example Use Cases in Automation

  1. Account Creation:
    • Create an account using a temporary email and retrieve the activation link sent to the inbox.
  2. Email Verification:
    • Test workflows requiring a verification code sent via email (e.g., two-factor authentication).
  3. Password Reset:
    • Automate password reset processes by fetching the reset link or token from the inbox.
  4. Email-Based Notifications:
    • Test if email notifications (e.g., order confirmations, welcome messages) are sent correctly.

How MailJS Works

  1. Account Creation:
    • Use the API to generate a new temporary email account with a unique username and password.
  2. Receiving Emails:
    • Monitor the inbox of the generated email account for new messages.
  3. Reading Emails:
    • Retrieve emails as JSON data, which includes details like the sender, subject, and content.
  4. Email Data Extraction:
    • Parse the email content to extract specific information, such as a verification code or link.
  • Verifies the page shows the text "All Your Earnings Calls in One Place" after login.
  • Clicks on the "Pricing" button and then "API Pricing" tab, ensuring that it shows the related API pricing information.

6. Payment Processing

Initiates the payment process, waits for the Stripe payment iframe, and enters payment information.

Stripe is a leading payment platform that integrates securely into websites using iframes. These iframes, part of Stripe Elements, isolate sensitive payment inputs like card details to ensure data security and compliance.

Key Aspects in Testing Stripe:

  • Iframe Handling: Testing requires targeting iframe content using methods like switchToIframe in tools such as Jest-puppeteer, Selenium or Cypress, as iframes function as separate windows.
  • Stripe Elements: Pre-built UI components simplify secure payment input integration.
  • Test Card Numbers: Stripe provides these to mimic real-world payment scenarios.

The primary challenge in testing Stripe's iframe stems from its secure, isolated domain, which prevents direct access by most automated testing tools like Selenium or Cypress. This requires testers to navigate additional complexities and implement tailored solutions.

Key Challenges:

  1. Iframe Context Switching
    • Test frameworks must "switch" their focus to the iframe's document before interacting with its elements.
  2. Cross-Origin Restrictions
    • Stripe's iframe content is hosted on a separate domain to ensure secure data handling.
    • Cross-origin policies limit direct access to iframe elements, requiring creative workarounds like JavaScript execution or proxy servers.
  3. Frame Busting
    • Stripe's iframe may redirect users to another domain (e.g., for 3D Secure).
    • Such navigation can disrupt test flows, requiring careful tracking of the user journey across domains.
  4. Synchronization Issues
    • Ensuring iframe content fully loads before interaction is critical but non-trivial.
    • This can cause race conditions if test scripts attempt actions before the iframe is ready, requiring retries or explicit wait conditions.
  5. Dynamic Content Handling
    • Stripe dynamically updates its iframe content based on user input (e.g., real-time validation of card details).
    • Tests must account for these updates by polling for changes or adding conditional logic.
  6. Error and Edge Case Testing
    • Simulating failures (e.g., declined payments) within the iframe often involves using Stripe's provided test card numbers, requiring precise test data setup.

Checkout is rendered in an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing Checkout within another iframe because some payment methods require redirecting to another page for payment confirmation.

  • Data isolation:
  • By loading the payment form within an iFrame from Stripe's domain, the card details are never directly accessible to your website's JavaScript code, protecting them from malicious actors. 
  • Reduced PCI scope:
  • Since your servers don't handle sensitive payment information, you have a smaller PCI compliance footprint to manage. 
  • Enhanced security controls:
  • Stripe has dedicated security measures in place to protect payment data within their own infrastructure, including encryption and fraud detection mechanisms. 
  • Cross-site scripting (XSS) protection:
  • Using an iFrame can help mitigate XSS attacks, where malicious scripts could attempt to steal sensitive information from your website
  • await expect(page).toClick("#plan-starter");
        await page.waitForSelector("#pay-button button", { timeout: 20_000 });
        await page.waitForSelector('iframe[name^="__privateStripeFrame"]', {
          timeout: 20_000,
        });
    
    
        const getStripeFrame = async () => {
          while (true) {
            const stripeFrame = page
              .frames()
              .find((frame) => frame.url().includes("elements-inner-payment"));
            if (stripeFrame) {
              return stripeFrame;
            }
            console.log(`Waiting for stripeFrame...`);
            await sleep(500);
          }
        };
        const stripeFrame = await getStripeFrame();
        await sleep(5_000);
        await stripeFrame.waitForSelector("#Field-numberInput", {
          timeout: 15_000,
        });
        await stripeFrame.click("#Field-numberInput");
        await stripeFrame.type("#Field-numberInput", "4000 0000 0000 0002", {
          delay: 100,
        });
        await stripeFrame.click("#Field-expiryInput");
        await stripeFrame.type("#Field-expiryInput", "0825", { delay: 100 });
        await stripeFrame.click("#Field-cvcInput");
        await stripeFrame.type("#Field-cvcInput", "123", { delay: 100 });
        try {
          await stripeFrame.click("#Field-postalCodeInput");
          await stripeFrame.type("#Field-postalCodeInput", "12345", { delay: 100 });
        } catch (error) {
          console.log("Postal code input not found, skipping:", error);
        }
    

  • Explanation:
    • Clicks the "starter" plan, waits for the payment button and Stripe iframe to load.
    • Defines a function getStripeFrame() to find and return the iframe with Stripe's payment form.
    • Inputs test card details (using an intentionally declined card 4000 0000 0000 0002) and handles exceptions for missing input fields (e.g., postal code).
  • Reference: https://docs.stripe.com/

7. Card Declined Handling

Verifies the declined payment, clears the input, and retries with a valid card.

await expect(page).toClick("#pay-button");
    await expect(page).toMatchTextContent("Your card has been declined.", {
      timeout: 3_000,
    });
    await sleep(2_000);
    await stripeFrame.click("#Field-numberInput");
    for (let i = 0; i < 16; i++) {
      // Better to use keyboard to clear the field that to directly manipulate the DOM.
      await page.keyboard.press("Backspace", { delay: 100 });
    }
    await stripeFrame.type("#Field-numberInput", "4242424242424242", {
      delay: 100,
    });
    await expect(page).toClick("#pay-button");
    await expect(page).toMatchTextContent(
      "Your payment was successfully processed.",
      { timeout: 15_000 }
    );
    await expect(page).toClick("#success-checkout-close-button");

  • Explanation:
    • Clicks the "pay" button and verifies the declined message appears.
    • Clears the card input and enters a valid test card (4242424242424242), then submits the payment and verifies a success message appears.

To confirm that your integration works correctly, simulate transactions without moving any money using special values in test mode.

Test cards let you simulate several scenarios:

  • Successful payments by card band or country
  • Card errors due to declines, fraud  or invalid data
  •  Disputes and refunds
  • Authentication with 3D Secure and PINs

8. API Key Generation

Verifies API key creation after payment.

await expect(page).toMatchTextContent("Loading your API Key", {
      timeout: 15_000,
    });
    await expect(page).toMatchTextContent("API Key Details", {
      timeout: 15_000,
    });
    await expect(page).toMatchTextContent(
      "To Create your API Key, please click the button below",
      { timeout: 15_000 }
    );
    await expect(page).toClick("span", { text: "Generate API Key" });
    await expect(page).toMatchTextContent(
      "Your API Key is used to authenticate with the EarningsCall API",
      { timeout: 15_000 }
    );


    // Locate the full title with a unique string.
    const apiKeySelector = await page
      .locator(".api-key-text:nth-child(1) .key")
      .waitHandle();
    const apiKeyText = await apiKeySelector?.evaluate((el) => el.textContent);
    console.log(`API Key: ${apiKeyText}`);

  • Explanation:
    • Verifies that the page displays prompts to create and view the API key.
    • Clicks the button to generate the key and logs the key for verification.

9. Subscription Management

Navigates to the "Subscriptions" page to verify plan details and logs out.

await expect(page).toClick(".profile-name");
    await page.evaluate(() => {
      const items = Array.from(document.querySelectorAll(".dropdown-item"));
      const targetItem = items.find(
        (item) => item.textContent.trim() === "Subscriptions"
      );
      if (targetItem) {
        targetItem.click();
      }
    });
    await expect(page).toMatchTextContent("Your current plan", {
      timeout: 5_000,
    });
    await expect(page).toMatchTextContent("Subscriptions", { timeout: 5_000 });
    await logoutUser(page);

  • Explanation:
    • Click the profile dropdown and select "Subscriptions".
    • Verifies that subscription details are shown and logs out.
    • logoutUser:

      const logoutUser = async (page) => {
        try {
          await expect(page).toClick('.profile-name');
          await page.evaluate(() => {
            const items = Array.from(document.querySelectorAll('.dropdown-item'));
            const targetItem = items.find(item => item.textContent.trim() === 'Logout');
            if (targetItem) {
              targetItem.click();
            }
          });
          console.log("User logged out successfully");
        } catch (error) {
          console.log("No user to logout:", error);
        }
      };

The logoutUser function is an asynchronous helper designed to log out a user from a web application using Puppeteer. Here's how it works:

  1. Clicks the Profile Name: It first attempts to click on an element with the class .profile-name to open a dropdown menu.
  2. Locates and Clicks Logout:
    • It uses page.evaluate to execute client-side JavaScript.
    • Collects all elements with the class .dropdown-item.
    • Searches for an item where the text content is exactly "Logout."
    • If the item is found, it triggers a click event on it.
  3. Logs Success or Handles Error:
    • Logs "User logged out successfully" if the process succeeds.
    • Logs "No user to logout" with the error if anything goes wrong .                              

       

10. Helper Functions and Assertions

expect(page).toMatchTextContent: This is a Jest/Puppeteer assertion that checks if a specific text is present on the page within the specified timeout

expect(instance).toMatchTextContent(matcher[, options])

  • instance <Page|Frame|ElementHandle> Context
  • matcher<string|RegExp> A text or a RegExp to match in the page.
  • options <Object> Optional parameters
    • polling<string|number> An interval at which the pageFunction is executed, defaults to raf. If polling is a number, then it is treated as an interval in milliseconds at which the function would be executed
    • timeout<number>  maximum time to wait for in milliseconds. Defaults to 30000 (30 seconds). Pass 0 to disable timeout.
    • traverseShadowRoots<boolean> Whether shadow roots should be traversed to find a match.

      await expect(page).toMatchTextContent("Subscriptions", { timeout: 5_000 });

  • expect(page).toClick: This assertion simulates a click on a button or link containing the specified text.

    expect(instance).toClick(selector[, options])

  • instance <Page|Frame|ElementHandle> Context
  • selector <string|MatchSelector> A selector or a MatchSelector to click on.
  • options <Object> Optional parameters
  • button <"left"|"right"|"middle"> Defaults to left.
  • count <number> defaults to 1. See UIEvent.detail.
  • delay <number> Time to wait between mousedown and mouseup in milliseconds. Defaults to 0.
  • text <string|RegExp> A text or a RegExp to match in element textContent.

 

await expect(page).toClick("button", { text: "Pricing" });

await page.waitForSelector: Waits for a specific element (identified by a CSS selector) to appear on the page within a timeout.

instance.waitForSelector(selector[, options])

  • instance <Page|Frame|ElementHandle> Context
  • selector <string|MatchSelector> A selector or a MatchSelector to wait.
  • options <Object> Optional parameters

    • visible: Waits for the element to exist in the DOM(Document Object Model) and be visible (not display: none or visibility: hidden). Default is false.
    • hidden: Waits for the element to be removed from the DOM or be hidden (display: none or visibility: hidden). Default is false.
    • timeout: Maximum time to wait in milliseconds (default: 30,000ms). Use to disable the timeout. Can be overridden with Page.setDefaultTimeout().              
    await page.waitForSelector("#pay-button button", { timeout: 20_000 });

11. Error Handling and Logging

  • Inside the makeApiCallWithRetries function, errors are caught, logged, and the function retries the request after a 5-second delay if the API call fails. Similarly, for the payment form, errors like the postal code field not being available are logged but not treated as failures.

 

12. Timeouts

  • Test timeouts: Several parts of the code (such as waiting for Stripe elements and verifying the API key generation) have timeout settings (e.g., timeout: 15_000) to avoid hanging tests when elements take too long to load or process.

Conclusion

This Jest test script is well-structured for simulating a user interacting with the checkout flow, handling API calls, and interacting with a payment system like Stripe. It uses Puppeteer to automate browser interactions and ensures that each part of the checkout process is working as expected by combining UI interactions and API validation.

https://github.com/argos-ci/jest-puppeteer/blob/main/packages/expect-puppeteer/README.md#api