Introduction

Welcome to the guide on testing Descope flows with Playwright.

Note: This guide covers creating test users manually for testing; however, you can also see our Dynamic Test Users Configuration guide if you would like the ability to create test users dynamically as part of the sign-up-or-in process.

Playwright Installation

Make sure you have Playwright already installed within your application. If not, you can do so here.

Descope Application Setup

To get started with Descope, an application needs to be setup within the Descope Console via the following steps:

  1. Visit the Descope Console and create a new project.
  2. Enter the desired name for your application.
  3. Get your projectId found in Settings/Project , and create a managementKey, found in Settings/Company/Management Keys/+ Mangement Key

Global Setup and Teardown for Descope Authentication

Configuration

First, you'll need a general configuration file to setup Playwright testing. Create a playwright.config.ts in the root directory.
// playwright.config.ts

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

const config = {
 testDir: "e2e",
 use: {
   /* Base URL to use in actions like `await page.goto('/')`. */
   baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || "http://localhost:3000",


   /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
   trace: "on-first-retry",
   storageState: "playwright/.auth/user.json",
 },
 webServer: {
   command: "yarn start",
   url: "http://127.0.0.1:3000",
   reuseExistingServer: !process.env.CI,
   stdout: "ignore",
   stderr: "pipe",
 },
 globalSetup: require.resolve("./e2e/auth.setup"),
 globalTeardown: require.resolve("./e2e/auth.teardown"),
 /* Configure projects for major browsers */
 projects: [
   {
     name: "chromium",
     use: { ...devices["Desktop Chrome"] },
   },


   {
     name: "firefox",
     use: { ...devices["Desktop Firefox"] },
   },


   {
     name: "webkit",
     use: { ...devices["Desktop Safari"] },
   },


   /* Test against mobile viewports. */
   {
     name: "Mobile Chrome",
     use: { ...devices["Pixel 5"] },
   },
   {
     name: "Mobile Safari",
     use: { ...devices["iPhone 12"] },
   },


   /* Test against branded browsers. */
   {
     name: "Microsoft Edge",
     use: { ...devices["Desktop Edge"], channel: "msedge" },
   },
   {
     name: "Google Chrome",
     use: { ...devices["Desktop Chrome"], channel: "chrome" },
   },
 ],
} // export the defineConfig(config)

Setup for Authentication

To have access to test user credentials within our tests we need to configure Playwright to use the Descope environment variables set in the .env file.
  • e2e/auth.setup.ts
// e2e/auth.setup.ts


import Descope from "@descope/node-sdk";
import { chromium, type FullConfig } from "@playwright/test";
import * as crypto from "crypto";


require("dotenv").config();


export const authFile = "playwright/.auth/user.json";


async function globalSetup(config: FullConfig) {
 const browser = await chromium.launch();
 const page = await browser.newPage();


 const testUser = crypto.randomBytes(20).toString("hex");
 process.env.TEST_USER = testUser;


 const descope = Descope({
   projectId: process.env.REACT_APP_DESCOPE_PROJECT_ID,
   managementKey: process.env.DESCOPE_MANAGEMENT_KEY,
 });


 await descope.management.user.createTestUser(testUser, "test@test.test");
 const magiclink = await descope.management.user.generateMagicLinkForTestUser(
   "email",
   testUser,
   "https://test.local"
 );
 const token = magiclink.data.link.split("?t=")[1];
 const auth = await descope.magicLink.verify(token);


 await page.goto(config.projects[0].use.baseURL);
 await page.evaluate(
   ([ds, dsr]) => {
     window.localStorage.setItem("DS", ds);
     window.localStorage.setItem("DSR", dsr);
   },
   [auth.data.sessionJwt, auth.data?.refreshJwt]
 );


 await page.context().storageState({ path: authFile });
 await browser.close();
} // export the globalSetup
Make sure the corresponding environment variables exist in your .env file

Teardown after Testing

Create a e2e/auth.teardown.ts script to clean up test users and reset the state:
// e2e/auth.teardown.ts


import Descope from "@descope/node-sdk";
import { type FullConfig } from "@playwright/test";
require("dotenv").config();


async function globalTeardown(config: FullConfig) {
 const descope = Descope({
   projectId: process.env.REACT_APP_DESCOPE_PROJECT_ID,
   managementKey: process.env.DESCOPE_MANAGEMENT_KEY,
 });
 await descope.management.user.delete(process.env.TEST_USER);
}

Implementing Tests with Authenticated State

With the global setup and teardown scripts handling authentication, your tests can focus on the application's functionality:

// Home.spec.tsx


import { expect, test } from "@playwright/test";


test("test", async ({ page }) => {
 await page.goto("/");
 await expect(page.getByText(/Hello, U2/i)).toBeVisible();
});

Running Playwright tests

Simply execute the following command to run tests

npx playwright test
NOTE: If you don't already have playwright installed, you'll need to install it using the following command: npx playwright install. If you're on a linux/mac system, you might also need to run npx playwright install msedge to run the Microsoft Edge tests locally, from the root user.

That's it! To see a full example of setting up e2e tests with Playwright and Descope, check out the Descope Playwright React Example.

Notes

WebAuthn Support

If you are looking to test with WebAuthn, you may need to set up a virtual authenticator. This can be done as follows:

  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();

  // Set up a virtual authenticator
  const authenticator = await context.newCDPSession(page);
  await authenticator.send('WebAuthn.enable');
  await authenticator.send('WebAuthn.addVirtualAuthenticator', {
    options: {
      protocol: 'ctap2',
      transport: 'internal',
      hasResidentKey: true,
      hasUserVerification: true,
      isUserVerified: true,
      automaticPresenceSimulation: false,
    }
  });