Skip to content

Commit

Permalink
test(playwright): enhance manager procedure mocking (#1254)
Browse files Browse the repository at this point in the history
  • Loading branch information
angeloashmore authored Dec 26, 2023
1 parent 707d04d commit 99f5aa6
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 251 deletions.
80 changes: 56 additions & 24 deletions playwright/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,40 +95,72 @@ test.run({ loggedIn: true, onboarded: false })(

Warning: Only use `loggedIn: true` when it's necessary for your test, it increases local test time (not in CI) by some seconds (≃ 3 secs).

### Mocking with `mockManagerProcedures`
### Mocking with `procedures.mock`

Use `mockManagerProcedures` function when you need to mock a manager procedure response.
With the way playwright intercept requests you need to give an array of procedures to mock.
You will have the possibility to return fake data on top of the existing one.
If you don't need to return data and / or let the manager execute the procedure at all, you can disable it with `execute` property, it will return an empty object to the UI.
Use the `procedures` fixture to mock manager procedure responses:

Example:
```ts
test.run()("I can ...", async ({ procedures }) => {
await procedures.mock("getState", ({ data }) => {
return {
...data,
customTypes: [],
};
});
});
```

`data` contains the unmocked procedure's response. You can use it in your mocked response.

If you don't need the unmocked procedure's data or don't want the manager to execute the procedure at all, you can disable the unmocked procedure with the `execute` option:

```ts
await mockManagerProcedures({
page: changesPage.page,
procedures: [
{
path: "getState",
data: (data) => ({
test.run()(
"I can ...",
async ({ procedures }) => {
await procedures.mock("project.checkIsTypeScript", () => false);
},
{ execute: false },
);
```

If you only want the procedure to be mocked a set number of times, set the `times` option to the number of times you want it to be mocked:

```ts
test.run()("I can ...", async ({ procedures }) => {
await procedures.mock(
"getState",
({ data }) => {
return {
...data,
libraries: emptyLibraries,
customTypes: [],
remoteCustomTypes: [],
remoteSlices: [],
}),
},
{
path: "prismicRepository.pushChanges",
execute: false,
};
},
],
{ times: 1 },
);
});
```

You may stack `procedure.mock` calls as many times and anywhere you want. The most recent mock for a procedure will be used first.

```ts
test.run()("I can ...", async ({ procedures }) => {
await procedures.mock("project.checkIsTypeScript", () => false);
// `project.checkIsTypeScript` will return `false`

// Perform actions...

await procedures.mock("project.checkIsTypeScript", () => true);
// `project.checkIsTypeScript` will now return `true`
});
```

Warning: Only mock when it's necessary because the state of Slice Machine or the remote repository can change.
We want to ensure test can be launched on any state of Slice Machine and any state of repository. Mocking will help you do that.
In theory, we want to avoid mocking while doing e2e tests. Smoke tests don't have any mocking but standalone tests can when it's necessary. It improves the DX and reduce the necessary setup that we can have for Smoke tests.
> [!CAUTION]
> Only mock when it's necessary because the state of Slice Machine or the remote repository can change.
>
> We want to ensure tests can be launched on any state of Slice Machine and any state of repository. Mocking will help you do that.
>
> In theory, we want to avoid mocking while doing e2e tests. Smoke tests don't have any mocking but standalone tests can when it's necessary. It improves the DX and reduce the necessary setup that we can have for Smoke tests.
## Best practices

Expand Down
13 changes: 13 additions & 0 deletions playwright/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { ChangelogPage } from "../pages/ChangelogPage";
import { SliceMachinePage } from "../pages/SliceMachinePage";
import { generateRandomId } from "../utils/generateRandomId";
import config from "../playwright.config";
import { MockManagerProcedures } from "../utils";

dotenv.config({ path: `.env.local` });

Expand All @@ -41,6 +42,11 @@ export type DefaultFixtures = {
reusableCustomType: { name: string };
singleCustomType: { name: string };
slice: { name: string };

/**
* Mocks
*/
procedures: MockManagerProcedures;
};

/**
Expand Down Expand Up @@ -308,6 +314,13 @@ export const defaultTest = (
// Propagate the modified page to the test
await use(page);
},

/**
* Mocks
*/
procedures: async ({ page }, use) => {
await use(await MockManagerProcedures.init(page));
},
});
};

Expand Down
100 changes: 33 additions & 67 deletions playwright/tests/changes/changes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect } from "@playwright/test";

import { test } from "../../fixtures";
import { mockManagerProcedures } from "../../utils";
import { emptyLibraries, simpleCustomType } from "../../mocks";

test.describe("Changes", () => {
Expand All @@ -26,21 +25,13 @@ test.describe("Changes", () => {

test.run({ loggedIn: true })(
"I can see the unauthorized screen when not authorized",
async ({ changesPage }) => {
await mockManagerProcedures({
page: changesPage.page,
procedures: [
{
path: "getState",
data: (data) => ({
...data,
clientError: {
status: 403,
},
}),
},
],
});
async ({ changesPage, procedures }) => {
procedures.mock("getState", ({ data }) => ({
...(data as Record<string, unknown>),
clientError: {
status: 403,
},
}));

await changesPage.goto();
await expect(changesPage.loginButton).not.toBeVisible();
Expand All @@ -50,22 +41,14 @@ test.describe("Changes", () => {

test.run({ loggedIn: true })(
"I can see the empty state when I don't have any changes to push",
async ({ changesPage }) => {
await mockManagerProcedures({
page: changesPage.page,
procedures: [
{
path: "getState",
data: (data) => ({
...data,
libraries: emptyLibraries,
customTypes: [],
remoteCustomTypes: [],
remoteSlices: [],
}),
},
],
});
async ({ changesPage, procedures }) => {
procedures.mock("getState", ({ data }) => ({
...(data as Record<string, unknown>),
libraries: emptyLibraries,
customTypes: [],
remoteCustomTypes: [],
remoteSlices: [],
}));

await changesPage.goto();
await expect(changesPage.loginButton).not.toBeVisible();
Expand All @@ -75,22 +58,14 @@ test.describe("Changes", () => {

test.run({ loggedIn: true })(
"I can see the changes I have to push",
async ({ changesPage }) => {
await mockManagerProcedures({
page: changesPage.page,
procedures: [
{
path: "getState",
data: (data) => ({
...data,
libraries: emptyLibraries,
customTypes: [simpleCustomType],
remoteCustomTypes: [],
remoteSlices: [],
}),
},
],
});
async ({ changesPage, procedures }) => {
procedures.mock("getState", ({ data }) => ({
...(data as Record<string, unknown>),
libraries: emptyLibraries,
customTypes: [simpleCustomType],
remoteCustomTypes: [],
remoteSlices: [],
}));

await changesPage.goto();
await expect(changesPage.loginButton).not.toBeVisible();
Expand All @@ -105,25 +80,16 @@ test.describe("Changes", () => {

test.run({ loggedIn: true })(
"I can push the changes I have",
async ({ changesPage }) => {
await mockManagerProcedures({
page: changesPage.page,
procedures: [
{
path: "getState",
data: (data) => ({
...data,
libraries: emptyLibraries,
customTypes: [simpleCustomType],
remoteCustomTypes: [],
remoteSlices: [],
}),
},
{
path: "prismicRepository.pushChanges",
execute: false,
},
],
async ({ changesPage, procedures }) => {
procedures.mock("getState", ({ data }) => ({
...(data as Record<string, unknown>),
libraries: emptyLibraries,
customTypes: [simpleCustomType],
remoteCustomTypes: [],
remoteSlices: [],
}));
procedures.mock("prismicRepository.pushChanges", () => undefined, {
execute: false,
});

await changesPage.goto();
Expand Down
Loading

0 comments on commit 99f5aa6

Please sign in to comment.