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:
parent
a14539b57c
commit
c7e03bbaba
|
|
@ -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).
|
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
|
## Further help
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,18 +182,6 @@
|
||||||
"options": {
|
"options": {
|
||||||
"lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
|
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
video: false,
|
||||||
|
e2e: {
|
||||||
|
setupNodeEvents(on, config) {},
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"baseUrl": "http://localhost:5000",
|
|
||||||
"integrationFolder": "cypress/integration",
|
|
||||||
"video": false
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
describe('New notebook form', () => {
|
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', () => {
|
it('should have a "New notebook" title', () => {
|
||||||
cy.visit('/new');
|
cy.visit('/new');
|
||||||
|
|
@ -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++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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: [],
|
|
||||||
};
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"namespace": "kubeflow-user",
|
||||||
|
"postHeaders": {
|
||||||
|
"X-XSRF-Token": "asdf",
|
||||||
|
"Cookie": "XSRF-TOKEN=asdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
@ -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
|
|
||||||
};
|
|
||||||
|
|
@ -1,43 +1,73 @@
|
||||||
import { notebook } from '../fixtures/notebook';
|
/// <reference types="cypress" />
|
||||||
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();
|
|
||||||
});
|
|
||||||
|
|
||||||
Cypress.Commands.add('selectAllNamespaces', () => {
|
Cypress.Commands.add('selectAllNamespaces', () => {
|
||||||
cy.intercept('GET', '/api/namespaces').as('getNamespaces');
|
|
||||||
cy.visit('/');
|
|
||||||
|
|
||||||
cy.log(`Selecting all namespaces`);
|
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-namespace-selector-dropdown]').click();
|
||||||
cy.get(`[data-cy-all-namespaces]`).click();
|
cy.get(`[data-cy-all-namespaces]`).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('createNotebook', () => {
|
Cypress.Commands.add('mockDashboardRequest', () => {
|
||||||
const randomSubfix = Math.random().toString(36).substring(4);
|
cy.intercept('GET', '/dashboard_lib.bundle.js', { body: [] }).as(
|
||||||
|
'mockDashboardRequest',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
notebook.name = `test-notebook-${randomSubfix}`;
|
Cypress.Commands.add('mockNamespacesRequest', () => {
|
||||||
notebook.namespace = settings.namespace;
|
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';
|
Cypress.Commands.add('mockNotebooksAllNamespacesRequest', settingsNamespace => {
|
||||||
const url = `api/namespaces/${settings.namespace}/notebooks`;
|
cy.fixture('namespaces').then(res => {
|
||||||
const body = notebook;
|
for (const namespace of res.namespaces) {
|
||||||
|
if (namespace === settingsNamespace) {
|
||||||
cy.request({ method, url, body, headers: settings.postHeaders }).then(() => {
|
continue;
|
||||||
return notebook;
|
}
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
});
|
|
||||||
|
|
@ -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
|
|
@ -16,8 +16,9 @@
|
||||||
"i18n:extract": "ng extract-i18n --output-path i18n",
|
"i18n:extract": "ng extract-i18n --output-path i18n",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"test:prod": "ng test --browsers=ChromeHeadless --watch=false",
|
"test:prod": "ng test --browsers=ChromeHeadless --watch=false",
|
||||||
"e2e": "cypress open .",
|
"ui-test": "cypress open . --e2e",
|
||||||
"e2e-ci": "cypress run .",
|
"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-check": "ng lint",
|
||||||
"lint": "ng lint --fix"
|
"lint": "ng lint --fix"
|
||||||
},
|
},
|
||||||
|
|
@ -66,7 +67,7 @@
|
||||||
"@types/node": "^12.20.15",
|
"@types/node": "^12.20.15",
|
||||||
"@typescript-eslint/eslint-plugin": "4.28.2",
|
"@typescript-eslint/eslint-plugin": "4.28.2",
|
||||||
"@typescript-eslint/parser": "4.28.2",
|
"@typescript-eslint/parser": "4.28.2",
|
||||||
"cypress": "^7.5.0",
|
"cypress": "^10.10.0",
|
||||||
"eslint": "^7.26.0",
|
"eslint": "^7.26.0",
|
||||||
"eslint-plugin-import": "latest",
|
"eslint-plugin-import": "latest",
|
||||||
"eslint-plugin-jsdoc": "^34.0.0",
|
"eslint-plugin-jsdoc": "^34.0.0",
|
||||||
|
|
@ -79,8 +80,8 @@
|
||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.6.0",
|
"karma-jasmine-html-reporter": "^1.6.0",
|
||||||
"prettier": "^2.3.1",
|
"prettier": "^2.3.1",
|
||||||
"protractor": "~7.0.0",
|
|
||||||
"ts-node": "^10.4.0",
|
"ts-node": "^10.4.0",
|
||||||
"typescript": "~4.2.4"
|
"typescript": "~4.2.4",
|
||||||
|
"wait-on": "^7.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue