Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C3] Address e2e flakiness #3877

Merged
merged 19 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strong-ways-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": minor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer needs a changeset?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done 👍

---

Verifies the existence of pages projects before attempting to deploy for added reliability.
20 changes: 11 additions & 9 deletions .github/workflows/test-c3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- packages/create-cloudflare/**

env:
node-version: 16.14
node-version: 18.17.1

jobs:
check:
Expand Down Expand Up @@ -36,6 +36,7 @@ jobs:
run: npm run test:unit -w create-cloudflare

cleanup:
name: "Cleanup Test Projects"
if: ${{ github.repository_owner == 'cloudflare' }}
runs-on: ubuntu-latest
steps:
Expand All @@ -55,16 +56,15 @@ jobs:

e2e:
needs: cleanup
name: "E2E"
name: ${{ format('E2E ({0}) {1}', matrix.pm, matrix.quarantine == true && '[quarantine]' || '') }}
continue-on-error: ${{ matrix.quarantine }}
if: |
github.event.pull_request.user.login != 'dependabot[bot]'
strategy:
matrix:
# TODO: add back windows
# os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest]
# pm: [npm, yarn, pnpm]
pm: [npm, pnpm]
quarantine: [true, false]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
Expand All @@ -80,8 +80,10 @@ jobs:
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.C3_TEST_CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.C3_TEST_CLOUDFLARE_ACCOUNT_ID }}
E2E_QUARANTINE: ${{ matrix.quarantine }}

get-dependabot-bumped-framework:
name: "Get bumped framework (dependabot-only)"
runs-on: ubuntu-latest
outputs:
bumped-framework-cli: ${{ steps.detect.outputs.result }}
Expand Down Expand Up @@ -129,14 +131,13 @@ jobs:
# framework (this is both for optimization and in order to reduce unnecessary flakiness)
e2e-only-dependabot-bumped-framework:
needs: [cleanup, get-dependabot-bumped-framework]
name: "Dependabot specific E2E"
name: ${{ format('Dependabot E2E ({0}) {1}', matrix.pm, matrix.quarantine == true && '[quarantine]' || '') }}
continue-on-error: ${{ matrix.quarantine }}
strategy:
matrix:
# TODO: add back windows
# os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest]
# pm: [npm, yarn, pnpm]
pm: [npm, pnpm]
quarantine: [true, false]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout Repo
Expand All @@ -153,3 +154,4 @@ jobs:
FRAMEWORK_CLI_TO_TEST: ${{ needs.get-bumped-framework.outputs.bumped-framework-cli }}
CLOUDFLARE_API_TOKEN: ${{ secrets.C3_TEST_CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.C3_TEST_CLOUDFLARE_ACCOUNT_ID }}
E2E_QUARANTINE: ${{ matrix.quarantine }}
214 changes: 109 additions & 105 deletions packages/create-cloudflare/e2e-tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,124 +4,128 @@ import { join } from "path";
import { beforeEach, afterEach, describe, test, expect } from "vitest";
import { version } from "../package.json";
import { frameworkToTest } from "./frameworkToTest";
import { keys, runC3 } from "./helpers";
import { isQuarantineMode, keys, runC3 } from "./helpers";

// Note: skipIf(frameworkToTest) makes it so that all the basic C3 functionality
// tests are skipped in case we are testing a specific framework
describe.skipIf(frameworkToTest)("E2E: Basic C3 functionality ", () => {
const tmpDirPath = realpathSync(mkdtempSync(join(tmpdir(), "c3-tests")));
const projectPath = join(tmpDirPath, "basic-tests");
describe.skipIf(frameworkToTest || isQuarantineMode())(
"E2E: Basic C3 functionality ",
() => {
const tmpDirPath = realpathSync(mkdtempSync(join(tmpdir(), "c3-tests")));
const projectPath = join(tmpDirPath, "basic-tests");

beforeEach(() => {
rmSync(projectPath, { recursive: true, force: true });
});

afterEach(() => {
if (existsSync(projectPath)) {
rmSync(projectPath, { recursive: true });
}
});
beforeEach(() => {
rmSync(projectPath, { recursive: true, force: true });
});

test("--version", async () => {
const { output } = await runC3({ argv: ["--version"] });
expect(output).toEqual(version);
});
afterEach(() => {
if (existsSync(projectPath)) {
rmSync(projectPath, { recursive: true });
}
});

test("--version with positionals", async () => {
const argv = "foo bar baz --version".split(" ");
const { output } = await runC3({ argv });
expect(output).toEqual(version);
});
test("--version", async () => {
const { output } = await runC3({ argv: ["--version"] });
expect(output).toEqual(version);
});

test("--version with flags", async () => {
const argv = "foo --type webFramework --no-deploy --version".split(" ");
const { output } = await runC3({ argv });
expect(output).toEqual(version);
});
test("--version with positionals", async () => {
const argv = "foo bar baz --version".split(" ");
const { output } = await runC3({ argv });
expect(output).toEqual(version);
});

test("Using arrow keys + enter", async () => {
const { output } = await runC3({
argv: [projectPath],
promptHandlers: [
{
matcher: /What type of application do you want to create/,
input: [keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
input: [keys.enter],
},
{
matcher: /Do you want to use git for version control/,
input: [keys.right, keys.enter],
},
{
matcher: /Do you want to deploy your application/,
input: [keys.left, keys.enter],
},
],
test("--version with flags", async () => {
const argv = "foo --type webFramework --no-deploy --version".split(" ");
const { output } = await runC3({ argv });
expect(output).toEqual(version);
});

expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});
test("Using arrow keys + enter", async () => {
const { output } = await runC3({
argv: [projectPath],
promptHandlers: [
{
matcher: /What type of application do you want to create/,
input: [keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
input: [keys.enter],
},
{
matcher: /Do you want to use git for version control/,
input: [keys.right, keys.enter],
},
{
matcher: /Do you want to deploy your application/,
input: [keys.left, keys.enter],
},
],
});

test("Typing custom responses", async () => {
const { output } = await runC3({
argv: [],
promptHandlers: [
{
matcher: /In which directory do you want to create your application/,
input: [projectPath, keys.enter],
},
{
matcher: /What type of application do you want to create/,
input: [keys.down, keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
input: ["n"],
},
{
matcher: /Do you want to use git for version control/,
input: ["n"],
},
{
matcher: /Do you want to deploy your application/,
input: ["n"],
},
],
expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});

expect(projectPath).toExist();
expect(output).toContain(`type Example router & proxy Worker`);
expect(output).toContain(`no typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});
test("Typing custom responses", async () => {
const { output } = await runC3({
argv: [],
promptHandlers: [
{
matcher:
/In which directory do you want to create your application/,
input: [projectPath, keys.enter],
},
{
matcher: /What type of application do you want to create/,
input: [keys.down, keys.enter],
},
{
matcher: /Do you want to use TypeScript/,
input: ["n"],
},
{
matcher: /Do you want to use git for version control/,
input: ["n"],
},
{
matcher: /Do you want to deploy your application/,
input: ["n"],
},
],
});

test("Mixed args and interactive", async () => {
const { output } = await runC3({
argv: [projectPath, "--ts", "--no-deploy"],
promptHandlers: [
{
matcher: /What type of application do you want to create/,
input: [keys.enter],
},
{
matcher: /Do you want to use git for version control/,
input: ["n"],
},
],
expect(projectPath).toExist();
expect(output).toContain(`type Example router & proxy Worker`);
expect(output).toContain(`no typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});

expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});
});
test("Mixed args and interactive", async () => {
const { output } = await runC3({
argv: [projectPath, "--ts", "--no-deploy"],
promptHandlers: [
{
matcher: /What type of application do you want to create/,
input: [keys.enter],
},
{
matcher: /Do you want to use git for version control/,
input: ["n"],
},
],
});

expect(projectPath).toExist();
expect(output).toContain(`type "Hello World" Worker`);
expect(output).toContain(`yes typescript`);
expect(output).toContain(`no git`);
expect(output).toContain(`no deploy`);
});
}
);
13 changes: 11 additions & 2 deletions packages/create-cloudflare/e2e-tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import crypto from "node:crypto";
import { tmpdir } from "os";
import { join } from "path";
import { spawn } from "cross-spawn";
import { sleep } from "helpers/common";
import { spinnerFrames } from "helpers/interactive";
import type { SpinnerStyle } from "helpers/interactive";

Expand Down Expand Up @@ -30,6 +31,7 @@ export type RunnerConfig = {
promptHandlers?: PromptHandler[];
argv?: string[];
outputPrefix?: string;
quarantine?: boolean;
};

export const runC3 = async ({
Expand All @@ -46,14 +48,17 @@ export const runC3 = async ({
const lines: string[] = data.toString().split("\n");
const currentDialog = promptHandlers[0];

lines.forEach((line) => {
lines.forEach(async (line) => {
// Uncomment to debug test output
if (filterLine(line)) {
console.log(`${outputPrefix} ${line}`);
}
stdout.push(line);

if (currentDialog && currentDialog.matcher.test(line)) {
// Add a small sleep to avoid input race
await sleep(500);

currentDialog.input.forEach((keystroke) => {
proc.stdin.write(keystroke);
});
Expand Down Expand Up @@ -103,7 +108,7 @@ export const testProjectDir = (suite: string) => {
mkdtempSync(join(tmpdir(), `c3-tests-${suite}`))
);

const randomSuffix = crypto.randomBytes(3).toString("hex");
const randomSuffix = crypto.randomBytes(4).toString("hex");
const baseProjectName = `${C3_E2E_PREFIX}${randomSuffix}`;

const getName = (suffix: string) => `${baseProjectName}-${suffix}`;
Expand Down Expand Up @@ -136,3 +141,7 @@ const filterLine = (line: string) => {

return true;
};

export const isQuarantineMode = () => {
return process.env.E2E_QUARANTINE === "true";
};
Loading