diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..28f0346 --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,43 @@ +name: Playwright Tests +on: + push: + branches: [main] + pull_request: + branches: [main] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + working-directory: dummy-e2e + run: npm ci + - name: Install ic-wasm + run: cargo install ic-wasm --version 0.3.5 + - name: Install Playwright Browsers + working-directory: dummy-e2e + run: npx playwright install --with-deps + - name: Install dfx + uses: dfinity/setup-dfx@main + - name: Start local replica + run: dfx start --background + - name: Deploy canisters + run: dfx deploy + - name: Prepare environment variables + working-directory: dummy-e2e + run: ./create-env-vars.sh + - name: Run Playwright tests + working-directory: dummy-e2e + run: npm run e2e + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 + - name: Stop local replica + run: dfx stop diff --git a/dfx.json b/dfx.json index 2eaa028..c7ae770 100644 --- a/dfx.json +++ b/dfx.json @@ -16,6 +16,11 @@ "wasm": "./dummy-issuer/dummy_issuer.wasm.gz", "build": "./dummy-issuer/build.sh", "shrink": false + }, + "internet_identity": { + "type": "custom", + "wasm": "https://github.com/dfinity/internet-identity/releases/download/release-2024-04-05/internet_identity_dev.wasm.gz", + "candid": "https://github.com/dfinity/internet-identity/releases/download/release-2024-04-05/internet_identity.did" } } } diff --git a/dummy-e2e/.gitignore b/dummy-e2e/.gitignore new file mode 100644 index 0000000..68c5d18 --- /dev/null +++ b/dummy-e2e/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/dummy-e2e/create-env-vars.sh b/dummy-e2e/create-env-vars.sh new file mode 100755 index 0000000..e77c7f8 --- /dev/null +++ b/dummy-e2e/create-env-vars.sh @@ -0,0 +1,17 @@ +# This script creates a .env file with the necessary environment variables +# for the local environment to run the tests. +# Needs to be executed within the same directory as the script. +ENV_FILE=${ENV_OUTPUT_FILE:-$PWD/.env} +echo "Creating .env file at $ENV_FILE" + +II_CANISTER_ID=$(dfx canister id internet_identity --network local) +ISSUER_CANISTER_ID=$(dfx canister id dummy_issuer --network local) +RP_CANISTER_ID=$(dfx canister id dummy_relying_party --network local) + +REPLICA_SERVER_PORT=$(dfx info webserver-port) +II_URL="http://${II_CANISTER_ID}.localhost:${REPLICA_SERVER_PORT}" +ISSUER_ORIGIN="http://${ISSUER_CANISTER_ID}.localhost:${REPLICA_SERVER_PORT}" +RP_ORIGIN="http://${RP_CANISTER_ID}.localhost:${REPLICA_SERVER_PORT}" +echo "II_URL=${II_URL}" > $ENV_FILE +echo "ISSUER_URL=${ISSUER_ORIGIN}" >> $ENV_FILE +echo "RP_URL=${RP_ORIGIN}" >> $ENV_FILE diff --git a/dummy-e2e/package-lock.json b/dummy-e2e/package-lock.json new file mode 100644 index 0000000..439c68d --- /dev/null +++ b/dummy-e2e/package-lock.json @@ -0,0 +1,105 @@ +{ + "name": "dummy-e2e", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "dummy-e2e", + "version": "0.0.1", + "license": "ISC", + "dependencies": { + "dotenv": "^16.4.5" + }, + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.14.2" + } + }, + "node_modules/@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "dependencies": { + "playwright": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + } + } +} diff --git a/dummy-e2e/package.json b/dummy-e2e/package.json new file mode 100644 index 0000000..c198dc9 --- /dev/null +++ b/dummy-e2e/package.json @@ -0,0 +1,18 @@ +{ + "name": "dummy-e2e", + "version": "0.0.1", + "description": "End to end tests for the dummy relying party and issuer.", + "scripts": { + "e2e": "playwright test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@playwright/test": "^1.44.1", + "@types/node": "^20.14.2" + }, + "dependencies": { + "dotenv": "^16.4.5" + } +} diff --git a/dummy-e2e/playwright.config.ts b/dummy-e2e/playwright.config.ts new file mode 100644 index 0000000..84449c6 --- /dev/null +++ b/dummy-e2e/playwright.config.ts @@ -0,0 +1,38 @@ +import { defineConfig, devices } from "@playwright/test"; +import dotenv from "dotenv"; + +dotenv.config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./src", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + screenshot: "only-on-failure", + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "retain-on-failure", + /* Uncomment if you want to see the e2e tests running in the browser */ + // headless: false, + testIdAttribute: "data-tid", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], +}); diff --git a/dummy-e2e/src/utils/sigin-in-user.utils.ts b/dummy-e2e/src/utils/sigin-in-user.utils.ts new file mode 100644 index 0000000..b7b847c --- /dev/null +++ b/dummy-e2e/src/utils/sigin-in-user.utils.ts @@ -0,0 +1,37 @@ +import { expect, type BrowserContext, type Page } from "@playwright/test"; + +export const signInWithNewUser = async ({ + page, + context, +}: { + page: Page; + context: BrowserContext; +}): Promise => { + const iiPagePromise = context.waitForEvent("page"); + + await page.locator("[data-tid=login-button]").click(); + + const iiPage = await iiPagePromise; + await expect(iiPage).toHaveTitle("Internet Identity"); + + await iiPage.locator("#registerButton").click(); + await iiPage.locator("[data-action=construct-identity]").click(); + + await iiPage.locator("input#captchaInput").fill("a"); + await iiPage.locator("#confirmRegisterButton").click(); + + try { + const anchor = await iiPage.locator("#userNumber").textContent(); + await iiPage.locator("#displayUserContinue").click(); + await iiPage.waitForEvent("close"); + await expect(iiPage.isClosed()).toBe(true); + + if (anchor === null) { + throw new Error("Anchor is null"); + } + return parseInt(anchor); + } catch (err) { + console.error("Error:", err); + return -1; + } +}; diff --git a/dummy-e2e/src/vc-flow.spec.ts b/dummy-e2e/src/vc-flow.spec.ts new file mode 100644 index 0000000..21dc0a7 --- /dev/null +++ b/dummy-e2e/src/vc-flow.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from "@playwright/test"; +import { signInWithNewUser } from "./utils/sigin-in-user.utils"; + +const RP_URL = process.env.RP_URL ?? ""; +const ISSUER_URL = process.env.ISSUER_URL ?? ""; +const II_URL = process.env.II_URL ?? ""; + +test("user gets credential from dummy issuer within the dummy relying party", async ({ + page, + context, +}) => { + await page.goto(RP_URL); + await expect(page).toHaveTitle("Dummy Relying Party"); + + await expect(await page.getByTestId("user-principal").isVisible()).toBe( + false + ); + + // Log in with a new user + await page.getByTestId("ii-url-input").fill(II_URL); + + await signInWithNewUser({ page, context }); + + await expect(await page.getByTestId("user-principal").isVisible()).toBe(true); + + // Fill credentials + await page.getByTestId("issuer-url-input").fill(ISSUER_URL); + + await page.getByTestId("credential-type-input").fill("Test"); + + await expect(await page.getByTestId("vc-result").textContent()).toBe("-"); + + // Request credentials + const iiPagePromise = context.waitForEvent("page"); + await page.getByTestId("request-credential-button").click(); + + const iiPage = await iiPagePromise; + await expect(iiPage).toHaveTitle("Internet Identity"); + await iiPage.locator("[data-action=allow]").click(); + await iiPage.waitForEvent("close"); + await expect(iiPage.isClosed()).toBe(true); + + await expect(await page.getByTestId("vc-result").textContent()).not.toBe("-"); +}); diff --git a/dummy-relying-party/frontend/index.html b/dummy-relying-party/frontend/index.html index 4ffbc95..3d6fdc3 100644 --- a/dummy-relying-party/frontend/index.html +++ b/dummy-relying-party/frontend/index.html @@ -12,12 +12,15 @@

Dummy relying party

- +
-

+

@@ -25,6 +28,7 @@

Dummy relying party

type="text" id="issuer-url" name="issuer-url" + data-tid="issuer-url-input" placeholder="Enter the issuer url" />
@@ -34,6 +38,7 @@

Dummy relying party

type="text" id="credential-type" name="credential-type" + data-tid="credential-type-input" placeholder="Enter the credential-type" />
@@ -51,12 +56,14 @@

Dummy relying party

@@ -64,16 +71,24 @@

Dummy relying party

- +
- +

Encoded Credential

-
-
+
-

Decoded Presentation

-

Decoded Credentials

diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..f2b26ce --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.77.2" +targets = ["wasm32-unknown-unknown"]