Thanks to visit codestin.com
Credit goes to github.com

Skip to content

chore(site): Make tests faster #6543

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

Merged
merged 8 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ jobs:
- name: Install node_modules
run: ./scripts/yarn_install.sh

- run: yarn test:ci
- run: yarn test:ci --max-workers ${{ steps.cpu-cores.outputs.count }}
working-directory: site

- uses: codecov/codecov-action@v3
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ site/test-results/*
site/e2e/test-results/*
site/e2e/states/*.json
site/playwright-report/*
site/.swc

# Make target for updating golden files.
cli/testdata/.gen-golden
Expand Down
36 changes: 18 additions & 18 deletions site/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
// REMARK: Jest is supposed to never exceed 50% maxWorkers by default. However,
// there seems to be an issue with this in our Ubuntu-based workspaces.
// If we don't limit it, then 100% CPU and high MEM usage is hit
// unexpectedly, leading to OOM kills.
//
// SEE thread: https://github.com/coder/coder/pull/483#discussion_r829636583
const maxWorkers = 2

module.exports = {
maxWorkers,
testTimeout: 10_000,
maxWorkers: 8,
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this value going to be set in CI anyways? It'd be ideal to use all 64 cores in dogfood

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, but when it happens into 64 cores at the same time, the output is kinda crazy. I think we can remove it if it is too slow tho but with 8 workers, it is kinda fast.

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense!

projects: [
{
globals: {
"ts-jest": {
tsconfig: "./tsconfig.test.json",
},
},
coverageReporters: ["text", "lcov"],
displayName: "test",
preset: "ts-jest",
roots: ["<rootDir>"],
setupFilesAfterEnv: ["./jest.setup.ts"],
extensionsToTreatAsEsm: [".ts"],
transform: {
"^.+\\.tsx?$": "ts-jest",
"\\.m?jsx?$": "jest-esm-transformer",
"^.+\\.(t|j)sx?$": [
"@swc/jest",
{
jsc: {
transform: {
react: {
runtime: "automatic",
},
},
experimental: {
plugins: [["jest_workaround", {}]],
},
},
},
],
},
testEnvironment: "jsdom",
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
Expand Down
20 changes: 3 additions & 17 deletions site/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,16 @@ import { server } from "./src/testHelpers/server"
import "jest-location-mock"
import { TextEncoder, TextDecoder } from "util"
import { Blob } from "buffer"
import { fetch, Request, Response, Headers } from "@remix-run/web-fetch"
import jestFetchMock from "jest-fetch-mock"

jestFetchMock.enableMocks()

global.TextEncoder = TextEncoder
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill for jsdom
global.TextDecoder = TextDecoder as any
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Polyfill for jsdom
global.Blob = Blob as any

// From REMIX https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/__tests__/setup.ts
if (!global.fetch) {
// Built-in lib.dom.d.ts expects `fetch(Request | string, ...)` but the web
// fetch API allows a URL so @remix-run/web-fetch defines
// `fetch(string | URL | Request, ...)`
// @ts-expect-error -- Polyfill for jsdom
global.fetch = fetch
// Same as above, lib.dom.d.ts doesn't allow a URL to the Request constructor
// @ts-expect-error -- Polyfill for jsdom
global.Request = Request
// web-std/fetch Response does not currently implement Response.error()
// @ts-expect-error -- Polyfill for jsdom
global.Response = Response
global.Headers = Headers
}

// Polyfill the getRandomValues that is used on utils/random.ts
Object.defineProperty(global.self, "crypto", {
value: {
Expand Down
14 changes: 8 additions & 6 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"@material-ui/icons": "4.5.1",
"@material-ui/lab": "4.0.0-alpha.42",
"@monaco-editor/react": "4.4.6",
"@remix-run/web-fetch": "4.3.2",
"@tanstack/react-query": "4.22.4",
"@testing-library/react-hooks": "8.0.1",
"@types/color-convert": "2.0.0",
Expand All @@ -59,6 +58,7 @@
"front-matter": "4.0.2",
"history": "5.3.0",
"i18next": "21.9.1",
"jest-environment-jsdom": "29.5.0",
"jest-location-mock": "1.0.9",
"just-debounce-it": "3.1.1",
"lodash": "4.17.21",
Expand Down Expand Up @@ -93,10 +93,12 @@
"@storybook/addon-essentials": "6.5.12",
"@storybook/addon-links": "6.5.9",
"@storybook/react": "6.5.12",
"@swc/core": "1.3.38",
"@swc/jest": "0.2.24",
"@testing-library/jest-dom": "5.16.4",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
"@types/jest": "27.4.1",
"@types/jest": "29.4.0",
"@types/node": "14.18.22",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
Expand All @@ -120,17 +122,17 @@
"eslint-plugin-react": "7.31.1",
"eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-unicorn": "44.0.0",
"jest": "27.5.1",
"jest": "29.5.0",
"jest-canvas-mock": "2.4.0",
"jest-esm-transformer": "1.0.0",
"jest-fetch-mock": "3.0.3",
"jest-runner-eslint": "1.1.0",
"jest-websocket-mock": "2.4.0",
"jest_workaround": "0.1.14",
"monaco-editor": "0.34.1",
"msw": "0.47.0",
"msw": "1.1.0",
"prettier": "2.8.1",
"resize-observer": "1.0.4",
"semver": "7.3.7",
"ts-jest": "27.1.4",
"typescript": "4.8.2"
},
"browserslist": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,6 @@ export const DeploymentBannerView: FC<DeploymentBannerViewProps> = ({
<Tooltip title="A countdown until stats are fetched again. Click to refresh!">
<Button
className={`${styles.value} ${styles.refreshButton}`}
title="Refresh"
onClick={() => {
if (fetchStats) {
fetchStats()
Expand Down
7 changes: 6 additions & 1 deletion site/src/components/Dialogs/ConfirmDialog/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ export const ConfirmDialog: FC<PropsWithChildren<ConfirmDialogProps>> = ({
}

return (
<Dialog className={styles.dialogWrapper} onClose={onClose} open={open}>
<Dialog
className={styles.dialogWrapper}
onClose={onClose}
open={open}
data-testid="dialog"
>
<div className={styles.dialogContent}>
<h3 className={styles.dialogTitle}>{title}</h3>
{description && (
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/DropdownButton/DropdownButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const DropdownButton: FC<DropdownButtonProps> = ({
const canOpen = secondaryActions.length > 0

return (
<span className={styles.buttonContainer}>
<span className={styles.buttonContainer} data-testid="workspace-actions">
{/* primary workspace CTA */}
<span data-testid="primary-cta" className={styles.primaryCta}>
{primaryAction}
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/Loader/FullScreenLoader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const FullScreenLoader: FC = () => {
const styles = useStyles()

return (
<div className={styles.root}>
<div className={styles.root} data-testid="loader">
Copy link
Member

@Kira-Pilot Kira-Pilot Mar 10, 2023

Choose a reason for hiding this comment

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

Is it still slow if we keep querying using getByRole but also use within? Just curious when we have to add a data-testid and when we can avoid it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, the getByRole does a lot of "math" behind the scenes to calculate the right role of an element. When we do a within, we reduce the number of elements the getByRole has to iterate over to find the right element.

<CircularProgress />
</div>
)
Expand Down
1 change: 1 addition & 0 deletions site/src/components/Loader/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const Loader: FC<React.PropsWithChildren<{ size?: number }>> = ({
display="flex"
alignItems="center"
justifyContent="center"
data-testid="loader"
>
<CircularProgress size={size} />
</Box>
Expand Down
29 changes: 6 additions & 23 deletions site/src/pages/CreateTemplatePage/CreateTemplatePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import {
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
renderWithAuth,
} from "testHelpers/renderHelpers"
import CreateTemplatePage from "./CreateTemplatePage"
import { screen, waitFor } from "@testing-library/react"
import { screen, waitFor, within } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import * as API from "api/api"

Expand Down Expand Up @@ -55,19 +53,19 @@ test("Create template with variables", async () => {
MockTemplateVersionVariable1,
MockTemplateVersionVariable2,
MockTemplateVersionVariable3,
MockTemplateVersionVariable4,
MockTemplateVersionVariable5,
])

// Render page, fill the name and submit
const { router } = await renderPage()
const { router, container } = await renderPage()
const form = container.querySelector("form") as HTMLFormElement
await userEvent.type(screen.getByLabelText(/Name/), "my-template")
await userEvent.click(
screen.getByRole("button", { name: /create template/i }),
within(form).getByRole("button", { name: /create template/i }),
)

// Wait for the variables form to be rendered and fill it
await screen.findByText(/Variables/)

// Type first variable
await userEvent.clear(screen.getByLabelText(/var.first_variable/))
await userEvent.type(
Expand All @@ -79,28 +77,15 @@ test("Create template with variables", async () => {
await userEvent.type(screen.getByLabelText(/var.second_variable/), "2")
// Select third variable on radio
await userEvent.click(screen.getByLabelText(/True/))
// Type fourth variable
await userEvent.clear(screen.getByLabelText(/var.fourth_variable/))
await userEvent.type(
screen.getByLabelText(/var.fourth_variable/),
"Fourth value",
)
// Type fifth variable
await userEvent.clear(screen.getByLabelText(/var.fifth_variable/))
await userEvent.type(
screen.getByLabelText(/var.fifth_variable/),
"Fifth value",
)
// Setup the mock for the second template version creation before submit the form
jest.clearAllMocks()
jest
.spyOn(API, "createTemplateVersion")
.mockResolvedValue(MockTemplateVersion)
jest.spyOn(API, "createTemplate").mockResolvedValue(MockTemplate)
await userEvent.click(
screen.getByRole("button", { name: /create template/i }),
within(form).getByRole("button", { name: /create template/i }),
)

await waitFor(() => expect(API.createTemplate).toBeCalledTimes(1))
expect(router.state.location.pathname).toEqual(
`/templates/${MockTemplate.name}`,
Expand All @@ -115,8 +100,6 @@ test("Create template with variables", async () => {
{ name: "first_variable", value: "First value" },
{ name: "second_variable", value: "2" },
{ name: "third_variable", value: "true" },
{ name: "fourth_variable", value: "Fourth value" },
{ name: "fifth_variable", value: "Fifth value" },
],
})
})
28 changes: 17 additions & 11 deletions site/src/pages/WorkspacePage/WorkspacePage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const renderWorkspacePage = async () => {
route: `/@${MockWorkspace.owner_name}/${MockWorkspace.name}`,
path: "/@:username/:workspace",
})

await waitForLoaderToBeRemoved()
}

Expand All @@ -46,9 +47,9 @@ const renderWorkspacePage = async () => {
*/
const testButton = async (label: string, actionMock: jest.SpyInstance) => {
const user = userEvent.setup()

await renderWorkspacePage()
const button = await screen.findByRole("button", { name: label })
const workspaceActions = screen.getByTestId("workspace-actions")
const button = within(workspaceActions).getByRole("button", { name: label })
await user.click(button)
expect(actionMock).toBeCalled()
}
Expand Down Expand Up @@ -86,32 +87,36 @@ afterAll(() => {

describe("WorkspacePage", () => {
it("requests a delete job when the user presses Delete and confirms", async () => {
const user = userEvent.setup()

const user = userEvent.setup({ delay: 0 })
const deleteWorkspaceMock = jest
.spyOn(api, "deleteWorkspace")
.mockResolvedValueOnce(MockWorkspaceBuild)
await renderWorkspacePage()

// open the workspace action popover so we have access to all available ctas
const trigger = await screen.findByTestId("workspace-actions-button")
const trigger = screen.getByTestId("workspace-actions-button")
await user.click(trigger)

const buttonText = t("actionButton.delete", { ns: "workspacePage" })

// Click on delete
const button = await screen.findByText(buttonText)
await user.click(button)

// Get dialog and confirm
const dialog = await screen.findByTestId("dialog")
const labelText = t("deleteDialog.confirmLabel", {
ns: "common",
entity: "workspace",
})
const textField = await screen.findByLabelText(labelText)
const textField = within(dialog).getByLabelText(labelText)
await user.type(textField, MockWorkspace.name)
const confirmButton = await screen.findByRole("button", { name: "Delete" })
const confirmButton = within(dialog).getByRole("button", {
name: "Delete",
hidden: false,
})
await user.click(confirmButton)
expect(deleteWorkspaceMock).toBeCalled()
// This test takes long to finish
}, 20_000)
})

it("requests a start job when the user presses Start", async () => {
server.use(
Expand Down Expand Up @@ -157,7 +162,8 @@ describe("WorkspacePage", () => {

await renderWorkspacePage()

const cancelButton = await screen.findByRole("button", {
const workspaceActions = screen.getByTestId("workspace-actions")
const cancelButton = within(workspaceActions).getByRole("button", {
name: "cancel action",
})

Expand Down
22 changes: 2 additions & 20 deletions site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { screen, waitFor } from "@testing-library/react"
import { screen } from "@testing-library/react"
import { rest } from "msw"
import * as CreateDayString from "util/createDayString"
import {
Expand Down Expand Up @@ -37,25 +37,7 @@ describe("WorkspacesPage", () => {
})

it("renders a filled workspaces page", async () => {
// When
const { container } = render(<WorkspacesPage />)

// Then
const nextPage = await screen.findByRole("button", { name: "Next page" })
expect(nextPage).toBeEnabled()
await waitFor(
async () => {
const prevPage = await screen.findByRole("button", {
name: "Previous page",
})
expect(prevPage).toBeDisabled()
const pageButtons = container.querySelectorAll(
`button[name="Page button"]`,
)
expect(pageButtons.length).toBe(2)
},
{ timeout: 2000 },
)
render(<WorkspacesPage />)
await screen.findByText(`${MockWorkspace.name}1`)
const templateDisplayNames = await screen.findAllByText(
`${MockWorkspace.template_display_name}`,
Expand Down
4 changes: 4 additions & 0 deletions site/src/testHelpers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,4 +307,8 @@ export const handlers = [
rest.get("/api/v2/appearance", (req, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockAppearance))
}),

rest.get("/api/v2/deployment/stats", (_, res, ctx) => {
return res(ctx.status(200), ctx.json(M.MockDeploymentStats))
}),
]
2 changes: 1 addition & 1 deletion site/src/testHelpers/renderHelpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,6 @@ export function renderWithAuth(
}

export const waitForLoaderToBeRemoved = (): Promise<void> =>
waitForElementToBeRemoved(() => screen.getByRole("progressbar"))
waitForElementToBeRemoved(() => screen.getByTestId("loader"))

export * from "./entities"
Loading