Skip to content

Commit

Permalink
Add .github/workflows/e2e.yml (#2968)
Browse files Browse the repository at this point in the history
* Add .github/workflows/e2e.yml

* Update yml name .github/workflows/e2e.yml

* Update from workflow_dispatch to on:push:branches:e2e-ci in .github/workflows/e2e.yml

* Remove yarn build .github/workflows/e2e.yml

* Add env ELECTRON_EXTRA_LAUNCH_ARGS to e2e.yml

* Add .github/workflows/e2e.yml

* Update yml name .github/workflows/e2e.yml

* Update from workflow_dispatch to on:push:branches:e2e-ci in .github/workflows/e2e.yml

* Remove yarn build .github/workflows/e2e.yml

* Add env ELECTRON_EXTRA_LAUNCH_ARGS to e2e.yml

* use 0.0.0.0

* remove wait-on

* use 0.0.0.0 in ghost server as well

* improve test resilience

* increase ghost server timeout

* increase app wait timeout

* increase app wait timeout even more :(

* run without cypress actions workflow

* correct syntax-error on working-directory

* use ubuntu latest

* use ubuntu latest

* Add npm install --save-dev tsconfig-paths

* Add yarn install =)

* Change DEFAULT_APP_HOSTNAME in e2e/lib/constants.ts

* Change DEFAULT_APP_HOSTNAME in e2e

* Add debug logs

* Switch --headful to --headless in e2e/index.ts

* Comment out options.headed{}

* Add fiftyone app connect command

* Fix line

* Add debugging

* Correct connect

* Add set -x

* Set remote to false

* Update DEFAULT_APP_ADDRESS

* Add debugging

* make local tests pass

* use event driven way to shut ghost server

* background server

* update name

* testing

* testing

* cypress

* experiment

* mv startup

* tweaks

* path fix

* no wait

* typo

* wait

* upload artifacts

* node change

* edit

* Add build steps

* Add step Install fiftyone in e2e.yml

* 0.0.0.0

* Add trigger and cleanup

* short circuit

* fix modal

* headed

* cleanup

* update workflows

* edits

* Add e2e to pr.yml and publish.yml

* edits

* exit code

* update e2e readme

---------

Co-authored-by: Sashank Aryal <[email protected]>
Co-authored-by: Benjamin Kane <[email protected]>
  • Loading branch information
3 people authored and lanzhenw committed May 15, 2023
1 parent adc2716 commit 4dada32
Show file tree
Hide file tree
Showing 19 changed files with 153 additions and 163 deletions.
74 changes: 74 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Test e2e

on: workflow_call

jobs:
test-e2e:
runs-on: ubuntu-latest
env:
FIFTYONE_DO_NOT_TRACK: true
ELECTRON_EXTRA_LAUNCH_ARGS: "--disable-gpu"
defaults:
run:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true

- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8

- name: Install dependencies
run: |
pip install --upgrade pip setuptools wheel
- name: Cache Node Modules
id: node-cache
uses: actions/cache@v3
with:
path: |
app/node_modules
app/.yarn/cache
key: node-modules-${{ hashFiles('app/yarn.lock') }}

- name: Install app
if: steps.node-cache.outputs.cache-hit != 'true'
run: cd app && yarn install

- name: Build app
run: make app

- name: Install fiftyone
run: pip install .

- name: Configure
id: test_config
run: |
python tests/utils/setup_config.py
python tests/utils/github_actions_flags.py
- name: FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v2

- name: Run e2e tests
run: |
FIFTYONE_DATABASE_NAME=cypress python ../fiftyone/server/main.py --address 0.0.0.0 --port 8787 &
yarn install
yarn start
working-directory: ./e2e
- name: Upload videos
uses: actions/upload-artifact@v2
with:
name: videos
path: e2e/cypress/videos

- name: Upload snapshots
uses: actions/upload-artifact@v2
with:
name: snapshots
path: e2e/cypress/snapshots/actual
5 changes: 4 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@ jobs:
build:
uses: ./.github/workflows/build.yml

e2e:
uses: ./.github/workflows/e2e.yml

test:
uses: ./.github/workflows/test.yml

all-tests:
runs-on: ubuntu-latest
needs: [build, test]
needs: [build, e2e, test]
if: always()
steps:
- run: sh -c ${{ needs.build.result == 'success' && needs.test.result == 'success' }}
7 changes: 5 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ jobs:
build:
uses: ./.github/workflows/build.yml

e2e:
uses: ./.github/workflows/e2e.yml

test:
uses: ./.github/workflows/test.yml

publish:
runs-on: ubuntu-latest
if: github.ref != 'refs/tags/docker-build'
needs: [build, test]
needs: [build, e2e, test]
steps:
- name: Download dist
uses: actions/download-artifact@v3
Expand All @@ -40,7 +43,7 @@ jobs:
python3 -m twine upload dist/*
build-image:
needs: [build, test]
needs: [build, e2e, test]
runs-on: ubuntu-20.04
steps:
- name: Clone fiftyone
Expand Down
6 changes: 5 additions & 1 deletion app/packages/components/src/components/Results/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ export const Result = <T extends unknown>({
const classes = active ? [style.active, style.result] : [style.result];

return (
<div onClick={onClick} className={classNames(...classes)}>
<div
data-cy="selector-result"
onClick={onClick}
className={classNames(...classes)}
>
<Component value={result} />
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion app/packages/looker-3d/src/Looker3d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export const Looker3d = () => {
useEffect(() => {
if (isPointcloudDataset) {
setCustomColorMap((prev) => {
if (Object.hasOwn(prev, "default")) {
if (prev && Object.hasOwn(prev, "default")) {
return prev;
}
return { ...(prev ?? {}), default: DEFAULT_GREEN };
Expand Down
6 changes: 5 additions & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ user. No mocks or stubs are to be used.

- Make sure `python` is available in path. If you have a virtual environment
you use, make sure to activate that.
- Run `yarn start`.
- e2e tests run against the built app. In the [App monorepo](../app/), run
`yarn build`
- Run `yarn start-test-server` to start the background server in a separate
shell
- Run `yarn start` to execute the tests
- To run in watch mode, run `yarn start:watch`.
- You might find it useful that all command line options are forwarded to
Cypress. For example, to run a single spec run
Expand Down
1 change: 1 addition & 0 deletions e2e/cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default defineConfig({
e2e: {
baseUrl: DEFAULT_APP_ADDRESS,
videoUploadOnPasses: false,
experimentalInteractiveRunEvents: true,
// retry once on test failure to account for random errors
// note: this is a global config, this can be configured per-test as well
retries: 1,
Expand Down
28 changes: 4 additions & 24 deletions e2e/cypress/e2e/3d/pointcloud-only-datasets.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,12 @@
*/

describe("pointcloud only datasets", () => {
let pId: number;

before(() => {
cy.executePythonFixture("3d/pointcloud-only-datasets - before.cy.py").then(
(pId_) => {
pId = pId_;
}
);

return cy.waitForFiftyOneApp();
});
before(() =>
cy.executePythonFixture("3d/pointcloud-only-datasets - before.cy.py")
);

beforeEach(() => {
cy.visit("/");
cy.get("[data-cy=fo-grid]").should("be.visible");
});

it("should show pointcloud dataset", () => {
cy.location("pathname").should(
"equal",
"/datasets/pointcloud-only-datasets"
);
cy.waitForGridToBeVisible("pointcloud-only-datasets");
});

it("should have one looker in one flashlight section", () => {
Expand All @@ -39,8 +23,4 @@ describe("pointcloud only datasets", () => {
cy.waitForLookerToRender();
cy.get("[data-cy=looker3d]").compareSnapshot("pcd-only-looker-modal-open");
});

after(() => {
cy.killFiftyoneApp(pId);
});
});
19 changes: 6 additions & 13 deletions e2e/cypress/e2e/quickstart-groups/quickstart-groups.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Duration } from "../../support/utils";

/**
* This test suite validates that quickstart-groups dataset from the zoo can be loaded in the app.
*/
Expand All @@ -11,21 +9,20 @@ const THIRD_SAMPLE_ID = "004416";
const SLICES = ["left", "pcd", "right"] as const;

describe("quickstart-groups dataset", () => {
let pId: number;

before(() => {
before(() =>
cy.executePythonFixture(
"quickstart-groups/quickstart-groups - before.cy.py"
).then((pId_) => {
pId = pId_;
});
)
);

return cy.waitForFiftyOneApp();
beforeEach(() => {
cy.waitForGridToBeVisible("quickstart-groups-12");
});

it("should have four lookers in two flashlight sections", () => {
cy.get("[data-cy=flashlight-section]")
.should("be.visible")

.and("have.length", 2);

cy.get("[data-cy=looker]").should("be.visible").and("have.length", 4);
Expand Down Expand Up @@ -159,10 +156,6 @@ describe("quickstart-groups dataset", () => {
});
});
});

after(() => {
cy.killFiftyoneApp(pId);
});
});

const getExpectedFileForQuickstartGroups = (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import fiftyone as fo

fo.delete_datasets("*")

# note, using __file__ won't work because this is a cypress fixture that gets renamed in runtime
dataset = fo.Dataset(name="pointcloud-only-datasets")
dataset = fo.Dataset(name="pointcloud-only-datasets", persistent=True)
pcd_sample = fo.Sample("cypress/fixtures/3d/resources/cone.pcd")
dataset.add_sample(pcd_sample)
session = fo.launch_app(dataset, remote=True)
session.wait()
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@
import fiftyone.zoo as foz

fo.delete_datasets("*")

quickstart_groups_dataset = foz.load_zoo_dataset(
"quickstart-groups", max_samples=12
)

session = fo.launch_app(quickstart_groups_dataset, remote=True)
session.wait()
quickstart_groups_dataset.persistent = True
45 changes: 18 additions & 27 deletions e2e/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,34 @@
// note: commands are executed in context of cypress (browser)
// to run code in node, delegate to `cy.task`

import { DEFAULT_APP_LOAD_TIMEOUT } from "../../lib/constants";
import { Duration } from "./utils";

Cypress.Commands.add("executePythonFixture", (pythonFixture) => {
return cy
Cypress.Commands.add("executePythonFixture", (pythonFixture) =>
cy
.fixture(pythonFixture)
.then((sourceCode) => cy.task("executePythonProcessTask", { sourceCode }));
});

Cypress.Commands.add("executePythonCode", (sourceCode) => {
return cy.task("executePythonProcessTask", { sourceCode });
});
.then((sourceCode) => cy.task("executePythonProcessTask", sourceCode))
);

Cypress.Commands.add(
"waitForLookerToRender",
(timeout = Duration.Seconds(0.4)) => {
cy.wait(timeout ?? Duration.LOOKER_RENDER_MAX_TIME_MS);
}
Cypress.Commands.add("executePythonCode", (sourceCode) =>
cy.task("executePythonProcessTask", sourceCode)
);

Cypress.Commands.add("killFiftyoneApp", (pId) => {
if (
process.env.NODE_ENV === "development" &&
Cypress.env("pause_between_tests")
) {
const logMessage = "Pausing Cypress to allow for debugging";
console.log(logMessage);
cy.consoleLog(logMessage);
cy.pause();
Cypress.Commands.add("waitForGridToBeVisible", (datasetName?: string) => {
if (datasetName) {
cy.visit(`/datasets/${datasetName}`);
} else {
cy.visit("/");
cy.get(`[data-cy="selector-Select dataset"]`).click();
cy.get("[data-cy=selector-result]").first().click();
}
cy.task("killProcessTask", { pId });

cy.get("[data-cy=fo-grid]").should("be.visible");
});

Cypress.Commands.add(
"waitForFiftyOneApp",
(timeout = DEFAULT_APP_LOAD_TIMEOUT) => {
return cy.task("waitForFiftyoneAppTask", timeout);
"waitForLookerToRender",
(timeout = Duration.Seconds(0.4)) => {
cy.wait(timeout ?? Duration.LOOKER_RENDER_MAX_TIME_MS);
}
);

Expand Down
6 changes: 0 additions & 6 deletions e2e/cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,3 @@ compareSnapshotCommand({
});

// todo: check if app port is available in beforeAll() and fail fast if not

// before each test, reset to the root route and wait for the grid to be visible
beforeEach(() => {
cy.visit("/");
cy.get("[data-cy=fo-grid]").should("be.visible");
});
43 changes: 2 additions & 41 deletions e2e/cypress/support/tasks.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,11 @@
import { DEFAULT_APP_ADDRESS } from "../../lib/constants";
import { PythonRunner } from "../../lib/python-runner";
import waitOn from "wait-on";

// note: these tasks in node context, i.e. outside of cypress
// note: `cy` is not available in task context
// note: https://docs.cypress.io/api/commands/task

export const customTasks = {
// task that executes arbitrary python process
executePythonProcessTask: ({ sourceCode }: { sourceCode: string }) => {
return PythonRunner.exec(sourceCode);
},
// task that kills a process with the given pId
killProcessTask: (props: { pId: number; signal?: string }) => {
const signal = props.signal ?? "SIGTERM";
const pId = props.pId;

try {
console.log(`Attempting to kill process ${pId}`);
process.kill(pId, signal);
console.log(`${signal} sent to process ${pId}`);
} catch {
console.log(`Process ${pId} doesn't exist`);
}

// need to return null to indicate success
return null;
},
// cy.log logs on the browser console, not in node
logTask: (message: string) => {
console.log(message);

// need to return null to indicate success
return null;
},
// poll and wait for fiftyone server to start
waitForFiftyoneAppTask: (timeout: number) => {
console.log(`Waiting for fiftyone app to start, timeout = ${timeout} ms`);
return new Promise((resolve, reject) =>
waitOn({
resources: [DEFAULT_APP_ADDRESS],
timeout: timeout,
})
// need to return null to indicate success
.then(() => resolve(null))
.catch(reject)
);
},
executePythonProcessTask: (sourceCode: string) =>
PythonRunner.exec(sourceCode),
};
Loading

0 comments on commit 4dada32

Please sign in to comment.