Skip to content

Commit

Permalink
feat(playwright): sharding support
Browse files Browse the repository at this point in the history
  • Loading branch information
gregberge committed Nov 3, 2023
1 parent 2728ff3 commit a495905
Show file tree
Hide file tree
Showing 14 changed files with 93 additions and 70 deletions.
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/buildkite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const service: Service = {
? Number(env.BUILDKITE_PULL_REQUEST)
: null,
prHeadCommit: null,
nonce: env.BUILDKITE_BUILD_ID || null,
};
},
};
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/circleci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const service: Service = {
runId: null,
prNumber: getPrNumber({ env }),
prHeadCommit: null,
nonce: env.CIRCLE_WORKFLOW_ID || env.CIRCLE_BUILD_NUM || null,
};
},
};
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const service: Service = {
runId: null,
prNumber: null,
prHeadCommit: null,
nonce: null,
};
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const service: Service = {
runId: env.GITHUB_RUN_ID || null,
prNumber: payload?.pull_request?.number || null,
prHeadCommit: payload?.pull_request?.head.sha ?? null,
nonce: `${env.GITHUB_RUN_ID}-${env.GITHUB_RUN_ATTEMPT}` || null,
};
},
};
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const service: Service = {
runId: null,
prNumber: null,
prHeadCommit: null,
nonce: env.CI_PIPELINE_ID || null,
};
},
};
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/heroku.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const service: Service = {
runId: null,
prNumber: null,
prHeadCommit: null,
nonce: env.HEROKU_TEST_RUN_ID || null,
}),
};

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/services/travis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const service: Service = {
runId: null,
prNumber: getPrNumber(ctx),
prHeadCommit: null,
nonce: env.TRAVIS_BUILD_ID || null,
};
},
};
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ci-environment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface CiEnvironment {
runId: string | null;
prNumber: number | null;
prHeadCommit: string | null;
nonce: string | null;
}

export interface Service {
Expand Down
41 changes: 12 additions & 29 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,26 @@
import { describe, it, expect } from "vitest";
import { createConfig } from "./config";
import { readConfig } from "./config";

describe("#createConfig", () => {
it("gets config", () => {
const config = createConfig();
config.load({ commit: "f16f980bd17cccfa93a1ae7766727e67950773d0" });
expect(config.get()).toEqual({
apiBaseUrl: "https://api.argos-ci.com/v2/",
const config = readConfig({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
branch: null,
token: null,
buildName: null,
parallel: false,
parallelNonce: null,
parallelTotal: null,
ciService: null,
jobId: null,
runId: null,
prNumber: null,
repository: null,
owner: null,
prHeadCommit: null,
referenceBranch: null,
referenceCommit: null,
});
expect(config.commit).toBe("f16f980bd17cccfa93a1ae7766727e67950773d0");
});

it("throws with invalid commit", () => {
expect(() => createConfig().validate()).toThrow("commit: Invalid commit");
expect(() => readConfig({ commit: "xx" })).toThrow(
"commit: Invalid commit",
);
});

it("throws with invalid token", () => {
const config = createConfig();
config.load({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token: "invalid",
});
expect(() => config.validate()).toThrow(
"token: Invalid Argos repository token (must be 40 characters)",
);
expect(() =>
readConfig({
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token: "invalid",
}),
).toThrow("token: Invalid Argos repository token (must be 40 characters)");
});
});
40 changes: 39 additions & 1 deletion packages/core/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import convict from "convict";
import { getCiEnvironment } from "./ci-environment";

const mustBeApiBaseUrl = (value: any) => {
const URL_REGEX =
Expand Down Expand Up @@ -141,8 +142,45 @@ export interface Config {
prHeadCommit: string | null;
}

export const createConfig = () => {
const createConfig = () => {
return convict<Config>(schema, {
args: [],
});
};

export const readConfig = (options: Partial<Config> = {}) => {
const config = createConfig();

const ciEnv = getCiEnvironment();

config.load({
apiBaseUrl: options.apiBaseUrl ?? config.get("apiBaseUrl"),
commit: options.commit ?? config.get("commit") ?? ciEnv?.commit ?? null,
branch: options.branch ?? config.get("branch") ?? ciEnv?.branch ?? null,
token: options.token ?? config.get("token") ?? null,
buildName: options.buildName ?? config.get("buildName") ?? null,
prNumber:
options.prNumber ?? config.get("prNumber") ?? ciEnv?.prNumber ?? null,
prHeadCommit: config.get("prHeadCommit") ?? ciEnv?.prHeadCommit ?? null,
referenceBranch:
options.referenceBranch ?? config.get("referenceBranch") ?? null,
referenceCommit:
options.referenceCommit ?? config.get("referenceCommit") ?? null,
ciService: ciEnv?.name ?? null,
owner: ciEnv?.owner ?? null,
repository: ciEnv?.repository ?? null,
jobId: ciEnv?.jobId ?? null,
runId: ciEnv?.runId ?? null,
parallel: options.parallel ?? config.get("parallel") ?? false,
parallelNonce:
options.parallelNonce ??
config.get("parallelNonce") ??
ciEnv?.nonce ??
null,
parallelTotal: options.parallelTotal ?? config.get("parallelTotal") ?? null,
});

config.validate();

return config.get();
};
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./upload";
export * from "./config";
45 changes: 8 additions & 37 deletions packages/core/src/upload.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createConfig } from "./config";
import { getCiEnvironment } from "./ci-environment";
import { readConfig } from "./config";
import { discoverScreenshots } from "./discovery";
import { optimizeScreenshot } from "./optimize";
import { hashFile } from "./hashing";
Expand Down Expand Up @@ -48,42 +47,14 @@ export interface UploadParameters {
referenceCommit?: string;
}

const getConfigFromOptions = (options: UploadParameters) => {
const config = createConfig();

const ciEnv = getCiEnvironment();

config.load({
apiBaseUrl: options.apiBaseUrl ?? config.get("apiBaseUrl"),
commit: options.commit ?? config.get("commit") ?? ciEnv?.commit ?? null,
branch: options.branch ?? config.get("branch") ?? ciEnv?.branch ?? null,
token: options.token ?? config.get("token") ?? null,
buildName: options.buildName ?? config.get("buildName") ?? null,
prNumber:
options.prNumber ?? config.get("prNumber") ?? ciEnv?.prNumber ?? null,
prHeadCommit: config.get("prHeadCommit") ?? ciEnv?.prHeadCommit ?? null,
referenceBranch:
options.referenceBranch ?? config.get("referenceBranch") ?? null,
referenceCommit:
options.referenceCommit ?? config.get("referenceCommit") ?? null,
ciService: ciEnv?.name ?? null,
owner: ciEnv?.owner ?? null,
repository: ciEnv?.repository ?? null,
jobId: ciEnv?.jobId ?? null,
runId: ciEnv?.runId ?? null,
const getConfigFromOptions = ({ parallel, ...options }: UploadParameters) => {
const config = readConfig({
...options,
parallel: Boolean(parallel),
parallelNonce: parallel ? parallel.nonce : null,
parallelTotal: parallel ? parallel.total : null,
});

if (options.parallel) {
config.load({
parallel: Boolean(options.parallel),
parallelNonce: options.parallel ? options.parallel.nonce : null,
parallelTotal: options.parallel ? options.parallel.total : null,
});
}

config.validate();

return config.get();
return config;
};

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/playwright/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"scripts": {
"prebuild": "rm -rf dist",
"build": "rollup -c",
"test": "pnpm exec playwright test",
"test": "pnpm exec playwright test --shard=2/4",
"e2e": "WITH_ARGOS_REPORTER=true pnpm run test"
}
}
26 changes: 24 additions & 2 deletions packages/playwright/src/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
TestCase,
TestResult,
} from "@playwright/test/reporter";
import { upload, UploadParameters } from "@argos-ci/core";
import { readConfig, upload, UploadParameters } from "@argos-ci/core";
import { randomBytes } from "node:crypto";
import { copyFile, mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
Expand All @@ -23,9 +23,27 @@ async function createTempDirectory() {

export type ArgosReporterOptions = Omit<UploadParameters, "files" | "root">;

const getParallelFromConfig = (
config: FullConfig,
): null | UploadParameters["parallel"] => {
if (!config.shard) return null;
if (config.shard.total === 1) return null;
const argosConfig = readConfig();
if (!argosConfig.parallelNonce) {
throw new Error(
"Playwright shard mode detected. Please specify ARGOS_PARALLEL_NONCE env variable. Read https://argos-ci.com/docs/parallel-testing",
);
}
return {
total: config.shard.total,
nonce: argosConfig.parallelNonce,
};
};

class ArgosReporter implements Reporter {
uploadDir!: string;
config: ArgosReporterOptions;
playwrightConfig!: FullConfig;

constructor(config: ArgosReporterOptions) {
this.config = config;
Expand All @@ -39,7 +57,8 @@ class ArgosReporter implements Reporter {
await writeFile(path, body);
}

async onBegin(_config: FullConfig, _suite: Suite) {
async onBegin(config: FullConfig, _suite: Suite) {
this.playwrightConfig = config;
this.uploadDir = await createTempDirectory();
}

Expand Down Expand Up @@ -82,10 +101,13 @@ class ArgosReporter implements Reporter {
}

async onEnd(_result: FullResult) {
const parallel = getParallelFromConfig(this.playwrightConfig);

try {
await upload({
files: ["**/*.png"],
root: this.uploadDir,
parallel: parallel ?? undefined,
...this.config,
});
} catch (error) {
Expand Down

0 comments on commit a495905

Please sign in to comment.