jwa(front): Add UI tests with Cypress (kubeflow/kubeflow#6891)

* jwa(front): Add integration tests with Cypress

 - Upgrade Cypress to version ^10.10.0
 - Add integration tests with Cypress to check that:
   * Index page renders every Notebook name into the table
   * Index page shows correct Status icon for all notebooks

Signed-off-by: Orfeas Kourkakis <orfeas@arrikto.com>

* gh-actions(jwa): Add UI tests to JWA's frontend workflow

Signed-off-by: Orfeas Kourkakis <orfeas@arrikto.com>

* fixup! gh-actions(jwa): Add UI tests to JWA's frontend workflow

Signed-off-by: Orfeas Kourkakis <orfeas@arrikto.com>
This commit is contained in:
Orfeas Kourkakis 2023-01-16 13:03:00 +02:00 committed by GitHub
parent a14539b57c
commit c7e03bbaba
21 changed files with 1283 additions and 1177 deletions

View File

@ -18,9 +18,22 @@ Run `ng build` to build the project. The build artifacts will be stored in the `
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
## Running integration tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
To run integration tests locally, make sure that node modules are installed and the frontend is serving the UI under `localhost:4200`. Then use `npm run e2e` to execute the integration tests via [Cypress](https://www.cypress.io/). This will open Cypress and there select the browser in which the tests will run.
Ideally, tests should be run both in Chrome and Firefox and for that there is the script `npm run e2e-ci-all` that `runs` (instead of `opening`) Cypress. Note that in order for tests to run in a browser, the browser needs to be already installed on the system.
Make sure to check out these guides for system-specific information on installing and running Cypress
- https://docs.cypress.io/guides/getting-started/installing-cypress
- https://docs.cypress.io/guides/references/advanced-installation
### WSL2
In order to be run in a WSL2 installation, Cypress requires these [dependencies](https://docs.cypress.io/guides/getting-started/installing-cypress#Linux-Prerequisites).
In the case of WSL2 on _Windows 10_, [this extra setup](https://docs.cypress.io/guides/references/advanced-installation#Windows-Subsystem-for-Linux) is required in order to have an X Server running in Windows host and creating the browser window.
## Further help

View File

@ -182,18 +182,6 @@
"options": {
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "frontend:serve"
},
"configurations": {
"production": {
"devServerTarget": "frontend:serve:production"
}
}
}
}
}

View File

@ -0,0 +1,9 @@
import { defineConfig } from 'cypress';
export default defineConfig({
video: false,
e2e: {
setupNodeEvents(on, config) {},
baseUrl: 'http://localhost:4200',
},
});

View File

@ -1,5 +0,0 @@
{
"baseUrl": "http://localhost:5000",
"integrationFolder": "cypress/integration",
"video": false
}

View File

@ -1,5 +1,15 @@
describe('New notebook form', () => {
beforeEach(() => {});
beforeEach(() => {
cy.mockDashboardRequest();
cy.mockStorageClassesRequests();
cy.mockDefaultStorageClassRequest('rok');
cy.mockGpusRequest();
cy.mockConfigRequest();
cy.fixture('settings').then(settings => {
cy.mockNotebooksRequest(settings.namespace);
cy.mockPoddefaultsRequest(settings.namespace);
});
});
it('should have a "New notebook" title', () => {
cy.visit('/new');

View File

@ -0,0 +1,87 @@
import { STATUS_TYPE } from 'kubeflow';
describe('Main table', () => {
beforeEach(() => {
cy.mockNamespacesRequest();
cy.fixture('settings').then(settings => {
cy.mockNotebooksRequest(settings.namespace);
});
cy.fixture('notebooks').as('notebooksRequest');
});
it('should have a "Notebooks" title', () => {
cy.visit('/');
cy.get('[data-cy-toolbar-title]').contains('Notebooks').should('exist');
});
it('should list Notebooks without errors', () => {
cy.visit('/');
// wait for the request to fetch notebooks and namespaces
cy.wait(['@mockNamespacesRequest', '@mockNotebooksRequest']);
// after fetching the data the page should not have an error snackbar
cy.get('[data-cy-snack-status=ERROR]').should('not.exist');
});
it('should have a `Namespace` column, when showing all-namespaces', () => {
cy.visit('/');
cy.wait(['@mockNamespacesRequest', '@mockNotebooksRequest']);
cy.fixture('settings').then(settings => {
cy.mockNotebooksAllNamespacesRequest(settings.namespace);
});
cy.selectAllNamespaces();
cy.get('[data-cy-table-header-row="Namespace"]').should('exist');
});
// We use function () in order to be able to access aliases via this
// tslint:disable-next-line: space-before-function-paren
it('renders every Notebook name into the table', function () {
cy.visit('/');
cy.wait(['@mockNamespacesRequest', '@mockNotebooksRequest']);
let i = 0;
const notebooks = this.notebooksRequest.notebooks;
// Table is sorted by Name in ascending order by default
// and pvcs object is also sorted alphabetically by name
cy.get(`[data-cy-resource-table-row="Name"]`).each(element => {
expect(element).to.contain(notebooks[i].name);
i++;
});
});
// tslint:disable-next-line: space-before-function-paren
it('checks Status icon for all notebooks', function () {
cy.visit('/');
cy.wait(['@mockNamespacesRequest', '@mockNotebooksRequest']);
let i = 0;
const notebooks = this.notebooksRequest.notebooks;
cy.get('[data-cy-resource-table-row="Status"]').each(element => {
if (notebooks[i].status.phase === STATUS_TYPE.READY) {
cy.wrap(element)
.find('lib-status>mat-icon')
.should('contain', 'check_circle');
} else if (notebooks[i].status.phase === STATUS_TYPE.STOPPED) {
cy.wrap(element)
.find('lib-status>lib-icon')
.should('have.attr', 'icon', 'custom:stoppedResource');
} else if (notebooks[i].status.phase === STATUS_TYPE.UNAVAILABLE) {
cy.wrap(element)
.find('lib-status>mat-icon')
.should('contain', 'timelapse');
} else if (notebooks[i].status.phase === STATUS_TYPE.WARNING) {
cy.wrap(element)
.find('lib-status>mat-icon')
.should('contain', 'warning');
} else if (
notebooks[i].status.phase === STATUS_TYPE.WAITING ||
notebooks[i].status.phase === STATUS_TYPE.TERMINATING
) {
cy.wrap(element).find('mat-spinner').should('exist');
}
i++;
});
});
});

View File

@ -0,0 +1,108 @@
{
"config": {
"affinityConfig": {
"options": [],
"value": ""
},
"allowCustomImage": true,
"configurations": {
"readOnly": false,
"value": []
},
"cpu": {
"limitFactor": "none",
"readOnly": false,
"value": "0.5"
},
"dataVolumes": {
"readOnly": false,
"value": []
},
"environment": {
"readOnly": false,
"value": {}
},
"gpus": {
"readOnly": false,
"value": {
"num": "none",
"vendor": "",
"vendors": [
{
"limitsKey": "nvidia.com/gpu",
"uiName": "NVIDIA"
},
{
"limitsKey": "amd.com/gpu",
"uiName": "AMD"
}
]
}
},
"hideRegistry": true,
"hideTag": false,
"image": {
"options": [
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-scipy:master-6e4ad3b4",
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-pytorch-full:master-6e4ad3b4",
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-pytorch-cuda-full:master-6e4ad3b4",
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-tensorflow-full:master-6e4ad3b4",
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-tensorflow-cuda-full:master-6e4ad3b4"
],
"value": "public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/jupyter-scipy:master-6e4ad3b4"
},
"imageGroupOne": {
"options": [
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/codeserver-python:master-e9324d39"
],
"value": "public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/codeserver-python:master-e9324d39"
},
"imageGroupTwo": {
"options": [
"public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/rstudio-tidyverse:master-e9324d39"
],
"value": "public.ecr.aws/j1r0q0g6/notebooks/notebook-servers/rstudio-tidyverse:master-e9324d39"
},
"imagePullPolicy": {
"readOnly": false,
"value": "IfNotPresent"
},
"memory": {
"limitFactor": "none",
"readOnly": false,
"value": "1.0Gi"
},
"shm": {
"readOnly": false,
"value": true
},
"storageClass": "{none}",
"tolerationGroup": {
"options": [],
"readOnly": false,
"value": ""
},
"workspaceVolume": {
"readOnly": false,
"value": {
"mount": "/home/jovyan",
"newPvc": {
"metadata": {
"name": "{notebook-name}-workspace"
},
"spec": {
"accessModes": ["ReadWriteOnce"],
"resources": {
"requests": {
"storage": "5Gi"
}
}
}
}
}
}
},
"status": 200,
"success": true,
"user": null
}

View File

@ -0,0 +1,17 @@
{
"namespaces": [
"auth",
"cert-manager",
"default",
"istio-system",
"knative-serving",
"kserve",
"kube-public",
"kube-system",
"kubeflow",
"kubeflow-user"
],
"status": 200,
"success": true,
"user": null
}

View File

@ -1,21 +0,0 @@
export const notebook = {
name: 'test-notebook',
namespace: 'default',
image:
'kubeflownotebookswg/jupyter-scipy:master-1831e436',
allowCustomImage: true,
imagePullPolicy: 'IfNotPresent',
customImage: '',
customImageCheck: false,
serverType: 'jupyter',
cpu: '0.5',
cpuLimit: '',
memory: '1Gi',
memoryLimit: '',
gpus: { num: 'none' },
affinityConfig: '',
tolerationGroup: '',
datavols: [],
shm: true,
configurations: [],
};

View File

@ -0,0 +1,250 @@
{
"notebooks": [
{
"age": "2022-09-01T15:15:35Z",
"cpu": "1m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/jupyter-scipy:latest",
"last_activity": "2022-09-21T12:15:06Z",
"memory": "1073741824m",
"metadata": {
"annotations": {
"notebooks.kubeflow.org/last-activity": "2022-09-21T12:15:06Z",
"notebooks.kubeflow.org/server-type": "jupyter"
},
"creationTimestamp": "2022-09-01T15:15:35Z",
"generation": 2,
"labels": {
"app": "a-dog-breed-katib"
},
"name": "a-dog-breed-katib-warning",
"namespace": "kubeflow-user",
"resourceVersion": "60018065",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/a-dog-breed-katib",
"uid": "ff8ba37f-ec1f-46d4-a007-abed1e1a9e2a"
},
"name": "a-dog-breed-katib",
"namespace": "kubeflow-user",
"serverType": "jupyter",
"shortImage": "jupyter-scipy:latest",
"status": {
"message": "Warning",
"phase": "warning",
"state": ""
},
"volumes": [
"dshm",
"dog-breed-nwmrc-tutorial-dog-breed-datavol-1-xrrmt-lf276",
"dog-breed-nwmrc-tutorial-dog-breed-workspace-24ntl-jcjlv"
]
},
{
"age": "2022-09-15T13:28:12Z",
"cpu": "500m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/codeserver-python:latest",
"last_activity": "2022-09-15T13:28:17Z",
"memory": "1Gi",
"metadata": {
"annotations": {
"notebooks.kubeflow.org/http-rewrite-uri": "/",
"notebooks.kubeflow.org/last-activity": "2022-09-15T13:28:17Z",
"notebooks.kubeflow.org/server-type": "group-one"
},
"creationTimestamp": "2022-09-15T13:28:12Z",
"generation": 2,
"labels": {
"access-ml-pipeline": "true",
"access-rok": "true",
"app": "a-test-01"
},
"name": "a-test-01",
"namespace": "kubeflow-user",
"resourceVersion": "60018244",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/a-test-01",
"uid": "bfc9453c-9d98-46fd-aa4e-3482aff96e8d"
},
"name": "a-test-01",
"namespace": "kubeflow-user",
"serverType": "group-one",
"shortImage": "codeserver-python:latest",
"status": {
"message": "Running",
"phase": "ready",
"state": ""
},
"volumes": ["dshm", "b-rstudio-workspace-svdl2"]
},
{
"age": "2022-09-19T16:39:10Z",
"cpu": "500m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/jupyter-scipy:latest",
"last_activity": "",
"memory": "1Gi",
"metadata": {
"annotations": {
"kubeflow-resource-stopped": "2022-10-07T09:50:48Z",
"notebooks.kubeflow.org/server-type": "jupyter"
},
"creationTimestamp": "2022-09-19T16:39:10Z",
"generation": 2,
"labels": {
"access-ml-pipeline": "true",
"app": "a0-new-image"
},
"name": "a0-new-image",
"namespace": "kubeflow-user",
"resourceVersion": "60167216",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/a0-new-image",
"uid": "6699d0a4-93d2-4373-8a40-48cce931f5fa"
},
"name": "a0-new-image",
"namespace": "kubeflow-user",
"serverType": "jupyter",
"shortImage": "jupyter-scipy:latest",
"status": {
"message": "No Pods are currently running for this Notebook Server.",
"phase": "stopped",
"state": ""
},
"volumes": [
"a0-new-image-workspace-d8pc2",
"a0-new-image-datavol-1-wlmfg",
"dshm"
]
},
{
"age": "2022-09-01T08:36:40Z",
"cpu": "500m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/rstudio-tidyverse:latest",
"last_activity": "2022-09-12T07:30:39Z",
"memory": "1Gi",
"metadata": {
"annotations": {
"notebooks.kubeflow.org/http-headers-request-set": "{\"X-RStudio-Root-Path\":\"/notebook/kubeflow-user/b-rstudio/\"}",
"notebooks.kubeflow.org/http-rewrite-uri": "/",
"notebooks.kubeflow.org/last-activity": "2022-09-12T07:30:39Z",
"notebooks.kubeflow.org/server-type": "group-two"
},
"creationTimestamp": "2022-09-01T08:36:40Z",
"generation": 2,
"labels": {
"access-ml-pipeline": "true",
"app": "b-rstudio"
},
"name": "b-rstudio",
"namespace": "kubeflow-user",
"resourceVersion": "60018168",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/b-rstudio",
"uid": "43ec159d-648a-4546-8985-3edbbc4d132c"
},
"name": "b-rstudio-terminating",
"namespace": "kubeflow-user",
"serverType": "group-two",
"shortImage": "rstudio-tidyverse:latest",
"status": {
"message": "Terminating",
"phase": "terminating",
"state": ""
},
"volumes": ["dshm", "b-rstudio-workspace-svdl2"]
},
{
"age": "2022-04-15T09:41:07Z",
"cpu": "1m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/jupyter-scipy:latest",
"last_activity": "2022-09-21T12:16:25Z",
"memory": "1073741824m",
"metadata": {
"annotations": {
"notebooks.kubeflow.org/last-activity": "2022-09-21T12:16:25Z",
"notebooks.kubeflow.org/server-type": "jupyter"
},
"creationTimestamp": "2022-04-15T09:41:07Z",
"generation": 2,
"labels": {
"access-ml-pipeline": "true",
"app": "inference"
},
"name": "inference-unavailable",
"namespace": "kubeflow-user",
"resourceVersion": "60019474",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/inference",
"uid": "e5670f15-9d3a-4226-b49e-2a680a911b09"
},
"name": "inference-unavailable",
"namespace": "kubeflow-user",
"serverType": "jupyter",
"shortImage": "jupyter-scipy:latest",
"status": {
"message": "unavailable",
"phase": "unavailable",
"state": ""
},
"volumes": ["dshm", "coronahack-l9dwt-1759876298-ax7mi-pvc-dfhvr-zqxmt"]
},
{
"age": "2022-04-14T08:25:10Z",
"cpu": "1m",
"gpus": {
"count": 0,
"message": ""
},
"image": "kubeflownotebookswg/jupyter-scipy:latest",
"last_activity": "2022-09-21T12:19:26Z",
"memory": "1073741824m",
"metadata": {
"annotations": {
"notebooks.kubeflow.org/last-activity": "2022-09-21T12:19:26Z",
"notebooks.kubeflow.org/server-type": "jupyter"
},
"creationTimestamp": "2022-04-14T08:25:10Z",
"generation": 2,
"labels": {
"access-ml-pipeline": "true",
"app": "random-forest"
},
"name": "random-forest",
"namespace": "kubeflow-user",
"resourceVersion": "53812193",
"selfLink": "/apis/kubeflow.org/v1beta1/namespaces/kubeflow-user/notebooks/random-forest",
"uid": "7637cafa-ba4d-4b60-93d3-7008b6ff2d67"
},
"name": "random-forest-waiting",
"namespace": "kubeflow-user",
"serverType": "jupyter",
"shortImage": "jupyter-scipy:latest",
"status": {
"message": "PodInitializing",
"phase": "waiting",
"state": ""
},
"volumes": [
"dshm",
"titanic-ml-47xh5-data-m57vq-2md82",
"titanic-ml-47xh5-tutorial-titanic-workspace-mxstz-52fbp"
]
}
],
"status": 200,
"success": true,
"user": null
}

View File

@ -0,0 +1,59 @@
{
"poddefaults": [
{
"apiVersion": "kubeflow.org/v1alpha1",
"desc": "Allow access to Kubeflow Pipelines",
"kind": "PodDefault",
"label": "access-ml-pipeline",
"metadata": {
"creationTimestamp": "2022-04-12T10:19:29Z",
"generation": 1,
"name": "access-ml-pipeline",
"namespace": "kubeflow-user",
"resourceVersion": "60754922",
"selfLink": "/apis/kubeflow.org/v1alpha1/namespaces/kubeflow-user/poddefaults/access-ml-pipeline",
"uid": "6ec1727b-7599-4100-afae-b6ff57a56bf8"
},
"spec": {
"desc": "Allow access to Kubeflow Pipelines",
"env": [
{
"name": "KF_PIPELINES_SA_TOKEN_PATH",
"value": "/var/run/secrets/kubeflow/pipelines/token"
}
],
"selector": {
"matchLabels": {
"access-ml-pipeline": "true"
}
},
"volumeMounts": [
{
"mountPath": "/var/run/secrets/kubeflow/pipelines",
"name": "volume-ml-pipeline-token",
"readOnly": true
}
],
"volumes": [
{
"name": "volume-ml-pipeline-token",
"projected": {
"sources": [
{
"serviceAccountToken": {
"audience": "pipelines.kubeflow.org",
"expirationSeconds": 99999,
"path": "token"
}
}
]
}
}
]
}
}
],
"status": 200,
"success": true,
"user": null
}

View File

@ -0,0 +1,7 @@
{
"namespace": "kubeflow-user",
"postHeaders": {
"X-XSRF-Token": "asdf",
"Cookie": "XSRF-TOKEN=asdf"
}
}

View File

@ -1,11 +0,0 @@
export const settings = {
// namespace to create test Notebooks in
namespace: 'default',
// POST/PATCH/PUT/DELETE requests will need these headers since we are
// using the double submit cookie CSRF mechanism
postHeaders: {
'X-XSRF-Token': 'asdf',
Cookie: 'XSRF-TOKEN=asdf',
},
};

View File

@ -1,35 +0,0 @@
import { settings } from '../fixtures/settings';
describe('Main table', () => {
beforeEach(() => {});
it('should have a "Notebook" title', () => {
cy.visit('/');
cy.get('[data-cy-toolbar-title]').contains('Notebooks').should('exist');
});
it('should list Notebooks without errors', () => {
cy.selectNamespace(settings.namespace);
// ensure there's at least one Notebook
cy.createNotebook();
// wait for the request to fetch notebooks
const getUrl = `/api/namespaces/${settings.namespace}/notebooks`;
cy.intercept('GET', getUrl).as('getNotebooks');
cy.wait('@getNotebooks');
// after fetching the data the page should not have an error snackbar
cy.get('[data-cy-snack-status=ERROR]').should('not.exist');
});
it('should have a `Namespace` column, when showing all-namespaces', () => {
cy.selectAllNamespaces();
// ensure there's at least one Notebook
cy.createNotebook().then(nb => {
cy.log(nb);
cy.get('[data-cy-table-header-row="Namespace"]').should('exist');
});
});
});

View File

@ -1,21 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// eslint-disable-next-line no-unused-vars
module.exports = (
on: Cypress.PluginEvents,
config: Cypress.PluginConfigOptions,
) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
};

View File

@ -1,43 +1,73 @@
import { notebook } from '../fixtures/notebook';
import { settings } from '../fixtures/settings';
Cypress.Commands.add('selectNamespace', ns => {
cy.intercept('GET', '/api/namespaces').as('getNamespaces');
cy.visit('/');
cy.log(`Selecting Namespace: ${ns}`);
cy.wait('@getNamespaces');
// click and select the provided namespace
cy.get('[data-cy-namespace-selector-dropdown]').click();
cy.get(`[data-cy-namespace=${ns}]`).click();
});
/// <reference types="cypress" />
Cypress.Commands.add('selectAllNamespaces', () => {
cy.intercept('GET', '/api/namespaces').as('getNamespaces');
cy.visit('/');
cy.log(`Selecting all namespaces`);
cy.wait('@getNamespaces');
// click and select the provided namespace
// click and select 'All namespaces' option
cy.get('[data-cy-namespace-selector-dropdown]').click();
cy.get(`[data-cy-all-namespaces]`).click();
});
Cypress.Commands.add('createNotebook', () => {
const randomSubfix = Math.random().toString(36).substring(4);
Cypress.Commands.add('mockDashboardRequest', () => {
cy.intercept('GET', '/dashboard_lib.bundle.js', { body: [] }).as(
'mockDashboardRequest',
);
});
notebook.name = `test-notebook-${randomSubfix}`;
notebook.namespace = settings.namespace;
Cypress.Commands.add('mockNamespacesRequest', () => {
cy.intercept('GET', '/api/namespaces', {
fixture: 'namespaces',
}).as('mockNamespacesRequest');
});
cy.log(`Creating a test Notebook ${notebook.namespace}/${notebook.name}`);
Cypress.Commands.add('mockNotebooksRequest', namespace => {
cy.intercept('GET', `/api/namespaces/${namespace}/notebooks`, {
fixture: 'notebooks',
}).as('mockNotebooksRequest');
});
const method = 'POST';
const url = `api/namespaces/${settings.namespace}/notebooks`;
const body = notebook;
cy.request({ method, url, body, headers: settings.postHeaders }).then(() => {
return notebook;
Cypress.Commands.add('mockNotebooksAllNamespacesRequest', settingsNamespace => {
cy.fixture('namespaces').then(res => {
for (const namespace of res.namespaces) {
if (namespace === settingsNamespace) {
continue;
}
cy.intercept('GET', `/api/namespaces/${namespace}/notebooks`, {
notebooks: [],
});
}
});
});
Cypress.Commands.add('mockStorageClassesRequests', () => {
cy.intercept('GET', '/api/storageclasses', {
storageClasses: ['rok', 'standard'],
});
});
Cypress.Commands.add('mockDefaultStorageClassRequest', defaultStorageClass => {
cy.intercept('GET', '/api/storageclasses/default', {
defaultStorageClass,
}).as('mockDefaultStorageClassRequest');
});
Cypress.Commands.add('mockGpusRequest', () => {
cy.intercept('GET', '/api/gpus', {
status: 200,
success: true,
user: null,
vendors: [],
}).as('mockGpusRequest');
});
Cypress.Commands.add('mockConfigRequest', () => {
cy.intercept('GET', '/api/config', {
fixture: 'config',
}).as('mockConfigRequest');
});
Cypress.Commands.add('mockPoddefaultsRequest', namespace => {
cy.intercept('GET', `/api/namespaces/${namespace}/poddefaults`, {
fixture: 'poddefaults',
}).as('mockPoddefaultsRequest');
});

View File

@ -0,0 +1,68 @@
import './commands';
// types of the custom commands
// Must be declared global to be detected by typescript (allows import/export)
declare global {
namespace Cypress {
interface Chainable {
/**
* Custom command to mock request at '/dashboard_lib.bundle.js'
*/
mockDashboardRequest(): Chainable<void>;
/**
* Custom command to select all-namespaces option from the dropdown
*/
selectAllNamespaces(): Chainable;
/**
* Custom command to mock request at '/api/namespaces'
*/
mockNamespacesRequest(): Chainable<void>;
/**
* Custom command to mock request at '/api/namespaces/<namespace>/notebooks'
* and returns array with mock notebooks []
*/
mockNotebooksRequest(namespace: string): Chainable<void>;
/**
* Custom command to mock request at '/api/namespaces/<namespace>/notebooks'
* for each namespace of namespaces fixture and returns array with mock notebooks []
*/
mockNotebooksAllNamespacesRequest(namespace: string): Chainable<void>;
/**
* Custom command to mock requests at
* - '/api/storageclasses'
* - '/api/rok/storageclasses'
*/
mockStorageClassesRequests(): Chainable<void>;
/**
* Custom command to mock requests at - '/api/storageclasses/default'
* and returns parameter defaultStorageClass
*/
mockDefaultStorageClassRequest(
defaultStorageClass: string,
): Chainable<void>;
/**
* Custom command to mock requests at '/api/gpus'
* and returns an object with empty vendors list [].
*/
mockGpusRequest(): Chainable<void>;
/**
* Custom command to mock requests at '/api/config'
*/
mockConfigRequest(): Chainable<void>;
/**
* Custom command to mock request at '/api/namespaces/<namespace>/poddefaults'
* and returns array with mock poddefaults []
*/
mockPoddefaultsRequest(namespace: string): Chainable<void>;
}
}
}

View File

@ -1,9 +0,0 @@
Cypress.on('uncaught:exception', (err, runnable) => {
// if testing the app locally, the backend server will return
// index.html back for the dashboard_lib.bundle.js
if (err.message.includes("Unexpected token '<'")) {
return false;
}
return true;
});

View File

@ -1,26 +0,0 @@
import './handlers';
import './commands';
// types of the custom commands
// Must be declared global to be detected by typescript (allows import/export)
declare global {
namespace Cypress {
interface Chainable {
/**
* Custom command to select a Namespace in the main page from the dropdown
* @param ns The namespace to select from the dropdown
*/
selectNamespace(ns: string): Chainable;
/**
* Custom command to select all-namespaces option from the dropdown
*/
selectAllNamespaces(): Chainable;
/**
* Custom command to create a Notebook with random suffixed name
*/
createNotebook(): Chainable;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,8 +16,9 @@
"i18n:extract": "ng extract-i18n --output-path i18n",
"test": "ng test",
"test:prod": "ng test --browsers=ChromeHeadless --watch=false",
"e2e": "cypress open .",
"e2e-ci": "cypress run .",
"ui-test": "cypress open . --e2e",
"ui-test-ci": "cypress run . --e2e",
"ui-test-ci-all": "cypress run . --e2e --browser=chrome && cypress run . --e2e --browser=firefox",
"lint-check": "ng lint",
"lint": "ng lint --fix"
},
@ -66,7 +67,7 @@
"@types/node": "^12.20.15",
"@typescript-eslint/eslint-plugin": "4.28.2",
"@typescript-eslint/parser": "4.28.2",
"cypress": "^7.5.0",
"cypress": "^10.10.0",
"eslint": "^7.26.0",
"eslint-plugin-import": "latest",
"eslint-plugin-jsdoc": "^34.0.0",
@ -79,8 +80,8 @@
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.6.0",
"prettier": "^2.3.1",
"protractor": "~7.0.0",
"ts-node": "^10.4.0",
"typescript": "~4.2.4"
"typescript": "~4.2.4",
"wait-on": "^7.0.1"
}
}