Skip to content
Draft
117 changes: 102 additions & 15 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
name: "Build list of E2E test projects"
runs-on: ubuntu-latest
outputs:
e2e-test-projects: ${{ steps.e2e-targets.outputs.e2e-test-projects }}
e2e-test-matrix: ${{ steps.e2e-targets.outputs.e2e-test-matrix }}
steps:
- name: Check out code
uses: actions/checkout@v6
Expand All @@ -39,26 +39,100 @@ jobs:
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Cache node_modules
id: nm-cache
uses: actions/cache@v4
with:
path: |
node_modules
**/node_modules
key: pnpm-nm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'pnpm-workspace.yaml') }}
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Create list of E2E test projects
if: steps.nm-cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build E2E matrix
id: e2e-targets
# Per-project shard count. Projects with count=1 run as a single job
# with --shard=1/1 (a no-op in Playwright) — for suites where the
# per-job setup overhead outweighs the parallelism win. Projects not
# listed here use the default count.
env:
SHARD_COUNTS: |
{
"@evervault/browser-e2e-tests": 1,
"@repo/crypto-harness-e2e-tests": 1
}
DEFAULT_SHARDS: 2
run: |-
E2E_PROJECTS=$(pnpm m ls --depth=1 --json | jq -cr '[.[] | select(.name|endswith("e2e-tests")) | .name]')
MATRIX=$(jq -cn \
--argjson projects "$E2E_PROJECTS" \
--argjson counts "$SHARD_COUNTS" \
--argjson default_shards "$DEFAULT_SHARDS" \
'[$projects[] as $p |
($counts[$p] // $default_shards) as $n |
range(1; $n + 1) | {project: $p, shard: ., "total-shards": $n}
]')
echo "e2e-test-matrix=$MATRIX" >> $GITHUB_OUTPUT
build:
name: "Build packages for E2E"
runs-on: ubuntu-latest
env:
VITE_API_URL: ${{ inputs.vite-api-url }}
VITE_EVERVAULT_JS_URL: ${{ inputs.vite-evervault-js-url }}
VITE_KEYS_URL: ${{ inputs.vite-keys-url }}
VITE_GOOGLE_PAY_MERCHANT_ID: ${{ vars.GOOGLE_PAY_MERCHANT_ID }}
VITE_TEST_COVERAGE: "${{ inputs.run-code-coverage-checks }}"
steps:
- name: Check out code
uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Setup Node.js environment
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Cache node_modules
id: nm-cache
uses: actions/cache@v4
with:
path: |
node_modules
**/node_modules
key: pnpm-nm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'pnpm-workspace.yaml') }}
- name: Install deps
if: steps.nm-cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build E2E dependencies
run: pnpm run --filter="{./e2e-tests/*}..." --if-present build
- name: Tar dist outputs
run: |-
E2E_TEST_PROJECTS=$(pnpm m ls --depth=1 --json | jq -cr '[.[] | select(.name|endswith("e2e-tests")) | .name]')
echo "e2e-test-projects=$E2E_TEST_PROJECTS" >> $GITHUB_OUTPUT
# The e2e tests run for quite a long time. Until we can accurately select the appropriate tests to run, we're running them in parallel (2 jobs per project, each with 3-4 worker processes.)
find packages -type d -name dist -not -path "*/node_modules/*" -print0 \
| tar --null -czf dist-outputs.tar.gz -T -
- name: Upload dist artifact
uses: actions/upload-artifact@v7
with:
name: e2e-dist-outputs
path: dist-outputs.tar.gz
retention-days: 1
if-no-files-found: error
# Larger e2e suites (inputs, ui-components) are split into 2 shards across
# separate runners; each shard runs Playwright with workers: "100%" so on an
# 8-core ev-runner-large it uses ~8 worker processes per shard. Smaller
# suites (browser, crypto-harness) run as a single job — setup overhead
# would otherwise dominate. The install + build is done once in the `build`
# job above and consumed here as an artifact.
e2e-test:
name: "Run E2E Tests for ${{ matrix.project }} (Shard ${{ matrix.shard }}/${{ matrix.total-shards }})"
needs: [get-e2e-targets]
name: "Run E2E Tests for ${{ matrix.project }} (${{ matrix.shard }}/${{ matrix.total-shards }})"
needs: [get-e2e-targets, build]
strategy:
fail-fast: false
matrix:
project: ${{ fromJson(needs.get-e2e-targets.outputs.e2e-test-projects) }}
shard: [1, 2]
include:
- total-shards: 2
include: ${{ fromJson(needs.get-e2e-targets.outputs.e2e-test-matrix) }}
timeout-minutes: 25
runs-on: ubuntu-latest
runs-on: ev-runner-large
container:
image: mcr.microsoft.com/playwright:v1.57.0-noble
permissions:
Expand Down Expand Up @@ -90,10 +164,23 @@ jobs:
with:
node-version-file: ".nvmrc"
cache: "pnpm"
- name: Cache node_modules
id: nm-cache
uses: actions/cache@v4
with:
path: |
node_modules
**/node_modules
key: pnpm-nm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'pnpm-workspace.yaml') }}
- name: Install deps
if: steps.nm-cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile --prefer-offline
- name: Build ${{ matrix.project }}
run: pnpm run -r --filter="${{ matrix.project }}..." --if-present build
- name: Download dist artifact
uses: actions/download-artifact@v7
with:
name: e2e-dist-outputs
- name: Extract dist outputs
run: tar -xzf dist-outputs.tar.gz && rm dist-outputs.tar.gz
- name: E2E test
id: run-e2e-test
if: ${{ inputs.update-screenshots == false }}
Expand Down
44 changes: 40 additions & 4 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ on:
description: "API endpoint"
required: true
type: string
base-ref:
description: "Git branch to diff against for incremental lint/test (e.g. 'master'). Leave empty to run on all packages."
required: false
type: string
default: ""
jobs:
lint:
name: "Lint and Test"
Expand All @@ -32,6 +37,9 @@ jobs:
steps:
- name: Check out code
uses: actions/checkout@v6
with:
# Needed so pnpm's [<since>] filter can resolve the base ref locally.
fetch-depth: 0
- uses: pnpm/action-setup@v6
- name: Setup Node.js environment
uses: actions/setup-node@v6
Expand All @@ -40,13 +48,41 @@ jobs:
cache: "pnpm"
- name: Install deps
run: pnpm install --frozen-lockfile
- name: Resolve pnpm filter
# When base-ref is provided, lint/test only the packages changed since
# that ref plus their dependents (so consumers of a changed package
# still get checked). Root-level changes (lockfile, .npmrc, workflow
# files, root package.json) force a full run since pnpm's [since]
# filter only tracks files inside workspace packages.
run: |-
BASE="${{ inputs.base-ref }}"
GLOBAL_RE='^(pnpm-lock\.yaml|package\.json|\.npmrc|pnpm-workspace\.yaml|\.github/)'
if [ -n "$BASE" ]; then
REF="origin/$BASE"
if ! git rev-parse --verify "$REF" >/dev/null 2>&1; then
REF="$BASE"
fi
if git diff --name-only "$REF"...HEAD | grep -qE "$GLOBAL_RE"; then
echo "Global config changed since $REF — running on all packages"
echo "PNPM_FILTER_LINT=--filter=@evervault/*" >> $GITHUB_ENV
echo "PNPM_FILTER_TEST=-r" >> $GITHUB_ENV
else
echo "Filtering to packages changed since $REF (and their dependents)"
echo "PNPM_FILTER_LINT=--filter=...[$REF]" >> $GITHUB_ENV
echo "PNPM_FILTER_TEST=--filter=...[$REF]" >> $GITHUB_ENV
fi
else
echo "No base-ref provided — running on all packages"
echo "PNPM_FILTER_LINT=--filter=@evervault/*" >> $GITHUB_ENV
echo "PNPM_FILTER_TEST=-r" >> $GITHUB_ENV
fi
- name: Formats check
run: pnpm run -r --filter="@evervault/*" --if-present format:check
run: pnpm run "$PNPM_FILTER_LINT" --if-present format:check
- name: Build
run: pnpm run -r --if-present build
- name: Typescript check
run: pnpm run -r --filter="@evervault/*" --if-present typecheck
run: pnpm run "$PNPM_FILTER_LINT" --if-present typecheck
- name: eslint check
run: pnpm run -r --filter="@evervault/*" --if-present lint
run: pnpm run "$PNPM_FILTER_LINT" --if-present lint
- name: Run unit tests
run: pnpm -r --if-present test
run: pnpm "$PNPM_FILTER_TEST" --if-present test
1 change: 1 addition & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
vite-evervault-js-url: "https://js.evervault.io/v2"
vite-keys-url: "https://keys.evervault.io"
vite-api-url: "https://api.evervault.io"
base-ref: ${{ github.base_ref }}
secrets: inherit
e2e-test:
uses: ./.github/workflows/e2e-test.yml
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/react-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ on:
paths:
- "examples/expo/**"
- "packages/react-native-v2/**"
- ".github/**"
- ".github/workflows/react-native.yml"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build_ios:
name: Build iOS Examples
Expand Down
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node-linker=hoisted
shamefully-hoist=true
network-concurrency=16
verify-store-integrity=false
4 changes: 2 additions & 2 deletions e2e-tests/inputs/tests/events.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ test.describe("evervault inputs", () => {
.fill(CardLib.validExpirationData);
await page.getByLabel("Security code").fill(CardLib.validSecurityCode);

await page.waitForTimeout(800);
await expect.poll(() => data?.isValid).toBe(true);

expect(calls).toBeGreaterThan(0);
expect(data.encryptedCard.number).toMatch(EV_STRING_REGEX);
Expand Down Expand Up @@ -73,7 +73,7 @@ test.describe("evervault inputs", () => {
.fill(CardLib.validExpirationData);
await page.getByLabel("Security code").fill(CardLib.validAmexCVV);

await page.waitForTimeout(800);
await expect.poll(() => data?.isValid).toBe(true);

expect(calls).toBeGreaterThan(0);
expect(data.encryptedCard.number).toMatch(EV_STRING_REGEX);
Expand Down
4 changes: 2 additions & 2 deletions e2e-tests/inputs/tests/magswipe.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ test.describe("evervault inputs", () => {
);
await page.getByLabel("Card number").press("Enter");

await page.waitForTimeout(500);
await expect.poll(() => data?.encryptedCard?.swipe).toBe(true);

expect(calls).toBeGreaterThan(0);
expect(data.encryptedCard.bin).toMatch("424242");
Expand Down Expand Up @@ -80,7 +80,7 @@ test.describe("evervault inputs", () => {
.type(";4242424242424242=3001123456?");
await page.getByLabel("Card number").press("Enter");

await page.waitForTimeout(500);
await expect.poll(() => data?.encryptedCard?.swipe).toBe(true);

expect(calls).toBeGreaterThan(0);

Expand Down
8 changes: 4 additions & 4 deletions e2e-tests/ui-components/tests/cardDetails.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -727,8 +727,8 @@ test.describe("card component", () => {
card.mount("#form");
});

await page.waitForTimeout(2000);
const frame = page.frameLocator("iframe[data-evervault]");
await expect(frame.getByLabel("Number")).toBeVisible();
await frame.getByLabel("Number").fill("4242424242424242");
await frame.getByLabel("Expiration").fill("12"); // intentionally incomplete
await frame.getByLabel("Expiration").blur();
Expand All @@ -744,8 +744,8 @@ test.describe("card component", () => {
card.mount("#form");
});

await page.waitForTimeout(2000);
const frame = page.frameLocator("iframe[data-evervault]");
await expect(frame.getByLabel("Number")).toBeVisible();
await frame.getByLabel("Number").fill("4242424242424242");
await frame.getByLabel("Expiration").fill("12"); // intentionally incomplete
await frame.getByLabel("Expiration").blur();
Expand All @@ -761,8 +761,8 @@ test.describe("card component", () => {
card.mount("#form");
});

await page.waitForTimeout(2000);
const frame = page.frameLocator("iframe[data-evervault]");
await expect(frame.getByLabel("Number")).toBeVisible();
await frame.getByLabel("Number").fill("4242424242424242");
await frame.getByLabel("Expiration").fill("12"); // intentionally incomplete
await frame.getByLabel("Expiration").blur();
Expand All @@ -775,8 +775,8 @@ test.describe("card component", () => {
card.mount("#form");
}, inlineTheme);

await page.waitForTimeout(2000);
const frame = page.frameLocator("iframe[data-evervault]");
await expect(frame.getByLabel("Number")).toBeVisible();
await frame.getByLabel("Number").fill("4242424242424242");
await frame.getByLabel("Expiration").fill("12"); // intentionally incomplete
await frame.getByLabel("Expiration").blur();
Expand Down