Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: turn e2e enterprise tests into e2e premium tests#14979

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
aslilac merged 5 commits intomainfrome2e-premium
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes fromall 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
27 changes: 12 additions & 15 deletions.github/workflows/ci.yaml
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -499,19 +499,19 @@ jobs:
working-directory: site

test-e2e:
runs-on: ${{ github.repository_owner == 'coder' && (matrix.variant.enterprise && 'depot-ubuntu-22.04' || 'depot-ubuntu-22.04-4') || 'ubuntu-latest' }}
# test-e2e fails on 2-core 8GB runners, so we use the 4-core 16GB runner
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-4' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ts == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
variant:
-enterprise: false
-premium: false
name: test-e2e
-enterprise: true
name: test-e2e-enterprise
-premium: true
name: test-e2e-premium
name: ${{ matrix.variant.name }}
steps:
- name: Checkout
Expand All@@ -535,38 +535,35 @@ jobs:
- run: pnpm playwright:install
working-directory: site

# Run tests that don't requirean enterprise license withoutan enterprise license
# Run tests that don't requirea premium license withouta premium license
- run: pnpm playwright:test --forbid-only --workers 1
if: ${{ !matrix.variant.enterprise }}
if: ${{ !matrix.variant.premium }}
env:
DEBUG: pw:api
working-directory: site

# Run all of the tests withan enterprise license
# Run all of the tests witha premium license
- run: pnpm playwright:test --forbid-only --workers 1
if: ${{ matrix.variant.enterprise }}
if: ${{ matrix.variant.premium }}
env:
DEBUG: pw:api
CODER_E2E_ENTERPRISE_LICENSE: ${{ secrets.CODER_E2E_ENTERPRISE_LICENSE }}
CODER_E2E_REQUIRE_ENTERPRISE_TESTS: "1"
CODER_E2E_LICENSE: ${{ secrets.CODER_E2E_LICENSE }}
CODER_E2E_REQUIRE_PREMIUM_TESTS: "1"
working-directory: site
# Temporarily allow these to fail so that I can gather data about which
# tests are failing.
continue-on-error: true

- name: Upload Playwright Failed Tests
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1
with:
name: failed-test-videos${{ matrix.variant.enterprise && '-enterprise' || '-agpl' }}
name: failed-test-videos${{ matrix.variant.premium && '-premium' || '' }}
path: ./site/test-results/**/*.webm
retention-days: 7

- name: Upload pprof dumps
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@604373da6381bf24206979c74d06a550515601b9 # v4.4.1
with:
name: debug-pprof-dumps${{ matrix.variant.enterprise && '-enterprise' || '-agpl' }}
name: debug-pprof-dumps${{ matrix.variant.premium && '-premium' || '' }}
path: ./site/test-results/**/debug-pprof-*.txt
retention-days: 7

Expand Down
21 changes: 18 additions & 3 deletionssite/e2e/constants.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -13,6 +13,9 @@ export const workspaceProxyPort = 3112;
export const agentPProfPort = 6061;
export const coderdPProfPort = 6062;

// The name of the organization that should be used by default when needed.
export const defaultOrganizationName = "coder";

// Credentials for the first user
export const username = "admin";
export const password = "SomeSecurePassword!";
Expand All@@ -34,10 +37,22 @@ export const gitAuth = {
installationsPath: "/installations",
};

export const requireEnterpriseTests = Boolean(
process.env.CODER_E2E_REQUIRE_ENTERPRISE_TESTS,
/**
* Will make the tests fail if set to `true` and a license was not provided.
*/
export const premiumTestsRequired = Boolean(
process.env.CODER_E2E_REQUIRE_PREMIUM_TESTS,
);
export const enterpriseLicense = process.env.CODER_E2E_ENTERPRISE_LICENSE ?? "";

export const license = process.env.CODER_E2E_LICENSE ?? "";

/**
* Certain parts of the UI change when organizations are enabled. Organizations
* are enabled by a license entitlement, and license configuration is guaranteed
* to run before any other tests, so having this as a bit of "global state" is
* fine.
*/
export const organizationsEnabled = Boolean(license);

// Disabling terraform tests is optional for environments without Docker + Terraform.
// By default, we opt into these tests.
Expand Down
42 changes: 41 additions & 1 deletionsite/e2e/expectUrl.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,8 @@ type PollingOptions = { timeout?: number; intervals?: number[] };

export const expectUrl = expect.extend({
/**
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL contains query parameters.
* toHavePathName is an alternative to `toHaveURL` that won't fail if the URL
* contains query parameters.
*/
async toHavePathName(page: Page, expected: string, options?: PollingOptions) {
let actual: string = new URL(page.url()).pathname;
Expand DownExpand Up@@ -34,4 +35,43 @@ export const expectUrl = expect.extend({
)}\nActual: ${this.utils.printReceived(actual)}`,
};
},

/**
* toHavePathNameEndingWith allows checking the end of the URL (ie. to make
* sure we redirected to a specific page) without caring about the entire URL,
* which might depend on things like whether or not organizations or other
* features are enabled.
*/
async toHavePathNameEndingWith(
page: Page,
expected: string,
options?: PollingOptions,
) {
let actual: string = new URL(page.url()).pathname;
let pass: boolean;
try {
await expect
.poll(() => {
actual = new URL(page.url()).pathname;
return actual.endsWith(expected);
}, options)
.toBe(true);
pass = true;
} catch {
pass = false;
}

return {
name: "toHavePathNameEndingWith",
pass,
actual,
expected,
message: () =>
`The page does not have the expected URL pathname.\nExpected a url ${
this.isNot ? "not " : ""
}ending with: ${this.utils.printExpected(
expected,
)}\nActual: ${this.utils.printReceived(actual)}`,
};
},
});
10 changes: 5 additions & 5 deletionssite/e2e/global.setup.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -28,16 +28,16 @@ test("setup deployment", async ({ page }) => {
await page.getByTestId("button-select-template").isVisible();

// Setup license
if (constants.requireEnterpriseTests || constants.enterpriseLicense) {
if (constants.premiumTestsRequired || constants.license) {
// Make sure that we have something that looks like a real license
expect(constants.enterpriseLicense).toBeTruthy();
expect(constants.enterpriseLicense.length).toBeGreaterThan(92); // the signature alone should be this long
expect(constants.enterpriseLicense.split(".").length).toBe(3); // otherwise it's invalid
expect(constants.license).toBeTruthy();
expect(constants.license.length).toBeGreaterThan(92); // the signature alone should be this long
expect(constants.license.split(".").length).toBe(3); // otherwise it's invalid

await page.goto("/deployment/licenses", { waitUntil: "domcontentloaded" });

await page.getByText("Add a license").click();
await page.getByRole("textbox").fill(constants.enterpriseLicense);
await page.getByRole("textbox").fill(constants.license);
await page.getByText("Upload License").click();

await expect(
Expand Down
101 changes: 68 additions & 33 deletionssite/e2e/helpers.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,9 +16,10 @@ import {
agentPProfPort,
coderMain,
coderPort,
enterpriseLicense,
defaultOrganizationName,
license,
premiumTestsRequired,
prometheusPort,
requireEnterpriseTests,
requireTerraformTests,
} from "./constants";
import { expectUrl } from "./expectUrl";
Expand All@@ -35,22 +36,28 @@ import {
type RichParameter,
} from "./provisionerGenerated";

// requiresEnterpriseLicense will skip the test if we're not running with an enterprise license
export function requiresEnterpriseLicense() {
if (requireEnterpriseTests) {
/**
* requiresLicense will skip the test if we're not running with a license added
*/
export function requiresLicense() {
if (premiumTestsRequired) {
return;
}

test.skip(!enterpriseLicense);
test.skip(!license);
}

// requireTerraformProvisioner by default is enabled.
/**
* requireTerraformProvisioner by default is enabled.
*/
export function requireTerraformProvisioner() {
test.skip(!requireTerraformTests);
}

// createWorkspace creates a workspace for a template.
// It does not wait for it to be running, but it does navigate to the page.
/**
* createWorkspace creates a workspace for a template. It does not wait for it
* to be running, but it does navigate to the page.
*/
export const createWorkspace = async (
page: Page,
templateName: string,
Expand DownExpand Up@@ -90,7 +97,7 @@ export const createWorkspace = async (

await expectUrl(page).toHavePathName(`/@admin/${name}`);

await page.waitForSelector("*[data-testid='build-status'] >> text=Running", {
await page.waitForSelector("[data-testid='build-status'] >> text=Running", {
state: "visible",
});
return name;
Expand DownExpand Up@@ -151,8 +158,10 @@ export const verifyParameters = async (
}
};

// StarterTemplates are ids of starter templates that can be used in place of
// the responses payload. These starter templates will require real provisioners.
/**
* StarterTemplates are ids of starter templates that can be used in place of
* the responses payload. These starter templates will require real provisioners.
*/
export enum StarterTemplates {
STARTER_DOCKER = "docker",
}
Expand All@@ -166,11 +175,14 @@ function isStarterTemplate(
return typeof input === "string";
}

// createTemplate navigates to the /templates/new page and uploads a template
// with the resources provided in the responses argument.
/**
* createTemplate navigates to the /templates/new page and uploads a template
* with the resources provided in the responses argument.
*/
export const createTemplate = async (
page: Page,
responses?: EchoProvisionerResponses | StarterTemplates,
orgName = defaultOrganizationName,
): Promise<string> => {
let path = "/templates/new";
if (isStarterTemplate(responses)) {
Expand All@@ -191,31 +203,47 @@ export const createTemplate = async (
});
}

// If the organization picker is present on the page, select the default
// organization.
const orgPicker = page.getByLabel("Belongs to *");
const organizationsEnabled = await orgPicker.isVisible();
if (organizationsEnabled) {
await orgPicker.click();
await page.getByText(orgName, { exact: true }).click();
}

const name = randomName();
await page.getByLabel("Name *").fill(name);
await page.getByTestId("form-submit").click();
await expectUrl(page).toHavePathName(`/templates/${name}/files`, {
timeout: 30000,
});
await expectUrl(page).toHavePathName(
organizationsEnabled
? `/templates/${orgName}/${name}/files`
: `/templates/${name}/files`,
{
timeout: 30000,
},
);
return name;
};

// createGroup navigates to the /groups/create page and creates a group with a
// random name.
/**
* createGroup navigates to the /groups/create page and creates a group with a
* random name.
*/
export const createGroup = async (page: Page): Promise<string> => {
await page.goto("/groups/create", { waitUntil: "domcontentloaded" });
await expectUrl(page).toHavePathName("/groups/create");

const name = randomName();
await page.getByLabel("Name", { exact: true }).fill(name);
await page.getByTestId("form-submit").click();
await expect(page).toHaveURL(
/\/groups\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
);
await expectUrl(page).toHavePathName(`/groups/${name}`);
return name;
};

// sshIntoWorkspace spawns a Coder SSH process and a client connected to it.
/**
* sshIntoWorkspace spawns a Coder SSH process and a client connected to it.
*/
export const sshIntoWorkspace = async (
page: Page,
workspace: string,
Expand DownExpand Up@@ -298,17 +326,21 @@ export const buildWorkspaceWithParameters = async (
});
};

// startAgent runs the coder agent with the provided token.
// It awaits the agent to be ready before returning.
/**
* startAgent runs the coder agent with the provided token. It waits for the
* agent to be ready before returning.
*/
export const startAgent = async (
page: Page,
token: string,
): Promise<ChildProcess> => {
return startAgentWithCommand(page, token, "go", "run", coderMain);
};

// downloadCoderVersion downloads the version provided into a temporary dir and
// caches it so subsequent calls are fast.
/**
* downloadCoderVersion downloads the version provided into a temporary dir and
* caches it so subsequent calls are fast.
*/
export const downloadCoderVersion = async (
version: string,
): Promise<string> => {
Expand DownExpand Up@@ -448,8 +480,10 @@ interface EchoProvisionerResponses {
apply?: RecursivePartial<Response>[];
}

// createTemplateVersionTar consumes a series of echo provisioner protobufs and
// converts it into an uploadable tar file.
/**
* createTemplateVersionTar consumes a series of echo provisioner protobufs and
* converts it into an uploadable tar file.
*/
const createTemplateVersionTar = async (
responses?: EchoProvisionerResponses,
): Promise<Buffer> => {
Expand DownExpand Up@@ -619,8 +653,10 @@ export const randomName = () => {
return randomUUID().slice(0, 8);
};

// Awaiter is a helper that allows you to wait for a callback to be called.
// It is useful for waiting for events to occur.
/**
* Awaiter is a helper that allows you to wait for a callback to be called. It
* is useful for waiting for events to occur.
*/
export class Awaiter {
private promise: Promise<void>;
private callback?: () => void;
Expand DownExpand Up@@ -825,7 +861,6 @@ export const updateTemplateSettings = async (
await page.goto(`/templates/${templateName}/settings`, {
waitUntil: "domcontentloaded",
});
await expectUrl(page).toHavePathName(`/templates/${templateName}/settings`);

for (const [key, value] of Object.entries(templateSettingValues)) {
// Skip max_port_share_level for now since the frontend is not yet able to handle it
Expand All@@ -839,7 +874,7 @@ export const updateTemplateSettings = async (
await page.getByTestId("form-submit").click();

const name = templateSettingValues.name ?? templateName;
await expectUrl(page).toHavePathName(`/templates/${name}`);
await expectUrl(page).toHavePathNameEndingWith(`/${name}`);
};

export const updateWorkspace = async (
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp