Skip to content

Commit

Permalink
Add e2e tests for Versions (#5329)
Browse files Browse the repository at this point in the history
* add e2e tests for Versions

* add how to run e2e tests locally to CONTRIBUTING.md

* fix: don't print `undefined` in spinner.stop in non-interactive environments
  • Loading branch information
RamIdeas authored Mar 20, 2024
1 parent 6d785dc commit c513522
Show file tree
Hide file tree
Showing 3 changed files with 331 additions and 1 deletion.
32 changes: 32 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,38 @@ When you open a PR to the `workers-sdk` repo, you should expect several checks t

A summary of this repositories actions can be found [here](.github/workflows/README.md)

## Running e2e tests locally

To run the e2e tests locally, you'll need a Cloudflare API Token and run:

```sh
$ WRANGLER="node ~/path/to/workers-sdk/packages/wrangler/wrangler-dist/cli.js" CLOUDFLARE_ACCOUNT_ID=$CLOUDFLARE_TESTING_ACCOUNT_ID CLOUDFLARE_API_TOKEN=$CLOUDFLARE_TESTING_API_TOKEN pnpm run test:e2e
```

You may optionally want to append a filename pattern to limit which e2e tests are run. Also you may want to set `--bail=n` to limit the number of fails tests to show the error before the rest of the tests finish running and to limit the noise in that output:

```sh
$ WRANGLER="node ~/path/to/workers-sdk/packages/wrangler/wrangler-dist/cli.js" CLOUDFLARE_ACCOUNT_ID=$CLOUDFLARE_TESTING_ACCOUNT_ID CLOUDFLARE_API_TOKEN=$CLOUDFLARE_TESTING_API_TOKEN pnpm run test:e2e [file-pattern] --bail=1
```

### Creating an API Token

1. Go to ["My Profile" > "User API Tokens"](https://dash.cloudflare.com/profile/api-tokens)
1. Click "Create API Token"
1. Use the "Edit Cloudflare Workers" template
1. Set "Account Resources" to "Include" "DevProd Testing" (you can use any account you have access to)
1. Set "Zone Resources" to "All zones from an account" and the same account as above
1. Click "Continue to summary"
1. Verify your token works by running the curl command provided
1. Set the environment variables in your terminal or in your profile file (e.g. ~/.zshrc, ~/.bashrc, ~/.profile, etc):

```sh
export CLOUDFLARE_TESTING_ACCOUNT_ID="<Account ID for the token you just created>"
export CLOUDFLARE_TESTING_API_TOKEN="<Token you just created>"
```

Note: Workers created in the e2e tests that fail might not always be cleaned up (deleted). Internal users with access to the "DevProd Testing" account can rely on an automated job to clean up the Workers based on the format of the name. If you use another account, please be aware you may want to manually delete the Workers yourself.

## Changesets

Every non-trivial change to the project - those that should appear in the changelog - must be captured in a "changeset".
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,9 @@ export const spinner = (
}
clearLoop();
} else {
logUpdate(`\n${grayBar} ${msg}`);
if (msg) {
logUpdate(`\n${grayBar} ${msg}`);
}
newline();
}
},
Expand Down
296 changes: 296 additions & 0 deletions packages/wrangler/e2e/versions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
import crypto from "node:crypto";
import path from "node:path";
import shellac from "shellac";
import { beforeAll, chai, describe, expect, it } from "vitest";
import { CLOUDFLARE_ACCOUNT_ID } from "./helpers/account-id";
import { normalizeOutput } from "./helpers/normalize";
import { dedent, makeRoot, seed } from "./helpers/setup";
import { WRANGLER } from "./helpers/wrangler-command";

chai.config.truncateThreshold = 1e6;

function matchWhoamiEmail(stdout: string): string {
return stdout.match(/associated with the email (.+?@.+?)!/)?.[1] as string;
}
function matchVersionId(stdout: string): string {
return stdout.match(/Version ID:\s+([a-f\d-]+)/)?.[1] as string;
}

describe("versions deploy", () => {
let root: string;
let workerName: string;
let workerPath: string;
let runInRoot: typeof shellac;
let runInWorker: typeof shellac;
let normalize: (str: string) => string;
let versionId1: string;
let versionId2: string;

beforeAll(async () => {
root = await makeRoot();
workerName = `tmp-e2e-wrangler-${crypto.randomBytes(4).toString("hex")}`;
workerPath = path.join(root, workerName);
runInRoot = shellac.in(root).env(process.env);
runInWorker = shellac.in(workerPath).env(process.env);
const email = matchWhoamiEmail(
(await runInRoot`$ ${WRANGLER} whoami`).stdout
);
normalize = (str) =>
normalizeOutput(str, {
[workerName]: "tmp-e2e-wrangler",
[email]: "[email protected]",
[CLOUDFLARE_ACCOUNT_ID]: "CLOUDFLARE_ACCOUNT_ID",
});
}, 50_000);

it("init worker", async () => {
const init =
await runInRoot`$ ${WRANGLER} init --yes --no-delegate-c3 ${workerName}`;

expect(normalize(init.stdout)).toContain(
"To publish your Worker to the Internet, run `npm run deploy`"
);

// TEMP: wrangler deploy needed for the first time to *create* the worker (will create 1 extra version + deployment in snapshots below)
await runInWorker`$ ${WRANGLER} deploy`;
});

it("upload worker version (with message and tag)", async () => {
const upload =
await runInWorker`$ ${WRANGLER} versions upload --message "Upload via e2e test" --tag "e2e-upload" --x-versions`;

versionId1 = matchVersionId(upload.stdout);

expect(normalize(upload.stdout)).toMatchInlineSnapshot(`
"Total Upload: xx KiB / gzip: xx KiB
Worker Version ID: 00000000-0000-0000-0000-000000000000
Uploaded tmp-e2e-wrangler (TIMINGS)"
`);
});

it("list 1 version", async () => {
const list = await runInWorker`$ ${WRANGLER} versions list --x-versions`;

expect(normalize(list.stdout)).toMatchInlineSnapshot(`
"Version ID: 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Author: [email protected]
Source: Unknown (version_upload)
Tag: e2e-upload
Message: Upload via e2e test
Version ID: 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Author: [email protected]
Source: Upload
Tag: -
Message: -"
`);

expect(list.stdout).toMatch(/Message:\s+Upload via e2e test/);
expect(list.stdout).toMatch(/Tag:\s+e2e-upload/);
});

it("deploy worker version", async () => {
const deploy =
await runInWorker`$ ${WRANGLER} versions deploy ${versionId1}@100% --message "Deploy via e2e test" --yes --x-versions`;

expect(normalize(deploy.stdout)).toMatchInlineSnapshot(`
"╭ Deploy Worker Versions by splitting traffic between multiple versions
├ Fetching latest deployment
├ Your current deployment has 1 version(s):
│ (100%) 00000000-0000-0000-0000-000000000000
│ Created: TIMESTAMP
│ Tag: -
│ Message: -
├ Fetching deployable versions
├ Which version(s) do you want to deploy?
├ 1 Worker Version(s) selected
├ Worker Version 1: 00000000-0000-0000-0000-000000000000
│ Created: TIMESTAMP
│ Tag: e2e-upload
│ Message: Upload via e2e test
├ What percentage of traffic should Worker Version 1 receive?
├ 100% of traffic
├ Add a deployment message
│ Deployment message Deploy via e2e test
├ Deploying 1 version(s)
╰ SUCCESS Deployed tmp-e2e-wrangler version 00000000-0000-0000-0000-000000000000 at 100% (TIMINGS)"
`);
});

it("list 1 deployment", async () => {
const list =
await runInWorker`$ ${WRANGLER} deployments list --x-versions`;

expect(normalize(list.stdout)).toMatchInlineSnapshot(`
"Created: TIMESTAMP
Author: [email protected]
Source: Unknown (deployment)
Message: Deploy via e2e test
Version(s): (100%) 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Tag: e2e-upload
Message: Upload via e2e test
Created: TIMESTAMP
Author: [email protected]
Source: Upload
Message: Automatic deployment on upload.
Version(s): (100%) 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Tag: -
Message: -"
`);
expect(list.stderr).toMatchInlineSnapshot('""');

expect(list.stdout).toContain(versionId1);
});

it("modify & upload worker version", async () => {
await seed(workerPath, {
"src/index.ts": dedent`
export default {
fetch(request) {
return new Response("Hello World AGAIN!")
}
}`,
});

const upload =
await runInWorker`$ ${WRANGLER} versions upload --message "Upload AGAIN via e2e test" --tag "e2e-upload-AGAIN" --x-versions`;

versionId2 = matchVersionId(upload.stdout);

expect(normalize(upload.stdout)).toMatchInlineSnapshot(`
"Total Upload: xx KiB / gzip: xx KiB
Worker Version ID: 00000000-0000-0000-0000-000000000000
Uploaded tmp-e2e-wrangler (TIMINGS)"
`);
});

it("list 2 versions", async () => {
const list = await runInWorker`$ ${WRANGLER} versions list --x-versions`;

expect(normalize(list.stdout)).toMatchInlineSnapshot(`
"Version ID: 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Author: [email protected]
Source: Unknown (version_upload)
Tag: e2e-upload-AGAIN
Message: Upload AGAIN via e2e test
Version ID: 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Author: [email protected]
Source: Unknown (version_upload)
Tag: e2e-upload
Message: Upload via e2e test
Version ID: 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Author: [email protected]
Source: Upload
Tag: -
Message: -"
`);

expect(list.stdout).toMatch(/Message:\s+Upload AGAIN via e2e test/);
expect(list.stdout).toMatch(/Tag:\s+e2e-upload-AGAIN/);
});

it("deploy worker version", async () => {
const deploy =
await runInWorker`$ ${WRANGLER} versions deploy ${versionId2}@100% --message "Deploy AGAIN via e2e test" --yes --x-versions`;

expect(normalize(deploy.stdout)).toMatchInlineSnapshot(`
"╭ Deploy Worker Versions by splitting traffic between multiple versions
├ Fetching latest deployment
├ Your current deployment has 1 version(s):
│ (100%) 00000000-0000-0000-0000-000000000000
│ Created: TIMESTAMP
│ Tag: e2e-upload
│ Message: Upload via e2e test
├ Fetching deployable versions
├ Which version(s) do you want to deploy?
├ 1 Worker Version(s) selected
├ Worker Version 1: 00000000-0000-0000-0000-000000000000
│ Created: TIMESTAMP
│ Tag: e2e-upload-AGAIN
│ Message: Upload AGAIN via e2e test
├ What percentage of traffic should Worker Version 1 receive?
├ 100% of traffic
├ Add a deployment message
│ Deployment message Deploy AGAIN via e2e test
├ Deploying 1 version(s)
╰ SUCCESS Deployed tmp-e2e-wrangler version 00000000-0000-0000-0000-000000000000 at 100% (TIMINGS)"
`);
});

it("list 2 deployments", async () => {
const list =
await runInWorker`$ ${WRANGLER} deployments list --x-versions`;

expect(normalize(list.stdout)).toMatchInlineSnapshot(`
"Created: TIMESTAMP
Author: [email protected]
Source: Unknown (deployment)
Message: Deploy AGAIN via e2e test
Version(s): (100%) 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Tag: e2e-upload-AGAIN
Message: Upload AGAIN via e2e test
Created: TIMESTAMP
Author: [email protected]
Source: Unknown (deployment)
Message: Deploy via e2e test
Version(s): (100%) 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Tag: e2e-upload
Message: Upload via e2e test
Created: TIMESTAMP
Author: [email protected]
Source: Upload
Message: Automatic deployment on upload.
Version(s): (100%) 00000000-0000-0000-0000-000000000000
Created: TIMESTAMP
Tag: -
Message: -"
`);
expect(list.stderr).toMatchInlineSnapshot('""');

expect(list.stdout).toContain(versionId1);
expect(list.stdout).toContain(versionId2);
});

// TODO: rollback, when supported for --x-versions

it("delete worker", async () => {
const { stdout, stderr } = await runInWorker`$ ${WRANGLER} delete`;

expect(normalize(stdout)).toMatchInlineSnapshot(`
"? Are you sure you want to delete tmp-e2e-wrangler? This action cannot be undone.
🤖 Using fallback value in non-interactive context: yes
Successfully deleted tmp-e2e-wrangler"
`);
expect(stderr).toMatchInlineSnapshot('""');
});
});

0 comments on commit c513522

Please sign in to comment.