123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- /*
- * Copyright 2018-2022 Elyra Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- describe('Pipeline Editor tests', () => {
- beforeEach(() => {
- cy.deleteFile('helloworld.yaml');
- cy.deleteFile('*.pipeline'); // delete pipeline files used for testing
- cy.bootstrapFile('invalid.pipeline');
- cy.bootstrapFile('helloworld.pipeline');
- cy.bootstrapFile('helloworld.ipynb');
- cy.exec('jupyter trust build/cypress-tests/helloworld.ipynb');
- cy.bootstrapFile('helloworld.py');
- cy.bootstrapFile('helloworld.r');
- cy.bootstrapFile('invalid.txt');
- cy.resetJupyterLab();
- });
- afterEach(() => {
- cy.deleteFile('helloworld.ipynb'); // delete notebook file used for testing
- cy.deleteFile('helloworld.py'); // delete python file used for testing
- cy.deleteFile('output.txt'); // delete output files generated by tests
- cy.deleteFile('*.pipeline'); // delete pipeline files used for testing
- cy.deleteFile('helloworld.yaml');
- cy.deleteFile('invalid.txt');
- // delete complex test directories
- cy.deleteFile('pipelines');
- cy.deleteFile('scripts');
- // delete runtime configurations used for testing
- cy.exec('elyra-metadata remove runtimes --name=kfp_test_runtime', {
- failOnNonZeroExit: false
- });
- cy.exec('elyra-metadata remove runtimes --name=airflow_test_runtime', {
- failOnNonZeroExit: false
- });
- // delete example catalogs used for testing
- cy.exec(
- 'elyra-metadata remove component-catalogs --name=example_components',
- {
- failOnNonZeroExit: false
- }
- );
- });
- // TODO: Fix Test is actually failing
- // it('empty editor should have disabled buttons', () => {
- // cy.focusPipelineEditor();
- // const disabledButtons = [
- // '.run-action',
- // '.export-action',
- // '.clear-action',
- // '.undo-action',
- // '.redo-action',
- // '.cut-action',
- // '.copy-action',
- // '.paste-action',
- // '.deleteSelectedObjects-action',
- // '.arrangeHorizontally-action',
- // '.arrangeVertically-action'
- // ];
- // checkDisabledToolbarButtons(disabledButtons);
- // const enabledButtons = [
- // '.save-action',
- // '.openRuntimes-action',
- // '.createAutoComment-action'
- // ];
- // checkEnabledToolbarButtons(enabledButtons);
- // closePipelineEditor();
- // });
- it('should block unsupported files', () => {
- cy.createPipeline();
- cy.dragAndDropFileToPipeline('invalid.txt');
- // check for unsupported files dialog message
- cy.findByText(/unsupported file/i).should('be.visible');
- // dismiss dialog
- cy.contains('OK').click();
- });
- it('populated editor should have enabled buttons', () => {
- cy.createPipeline();
- cy.checkTabMenuOptions('Pipeline');
- cy.addFileToPipeline('helloworld.ipynb'); // add Notebook
- cy.addFileToPipeline('helloworld.py'); // add Python Script
- cy.addFileToPipeline('helloworld.r'); // add R Script
- // check buttons
- const disabledButtons = [/redo/i, /cut/i, /copy/i, /paste/i, /delete/i];
- checkDisabledToolbarButtons(disabledButtons);
- const enabledButtons = [
- /run pipeline/i,
- /save pipeline/i,
- /export pipeline/i,
- /clear/i,
- /open runtimes/i,
- /open runtime images/i,
- /open component catalogs/i,
- /undo/i,
- /add comment/i,
- /arrange horizontally/i,
- /arrange vertically/i
- ];
- checkEnabledToolbarButtons(enabledButtons);
- });
- it('matches complex pipeline snapshot', () => {
- cy.bootstrapFile('pipelines/consumer.ipynb');
- cy.bootstrapFile('pipelines/create-source-files.py');
- cy.bootstrapFile('pipelines/producer-script.py');
- cy.bootstrapFile('pipelines/producer.ipynb');
- cy.bootstrapFile('scripts/setup.py');
- cy.bootstrapFile('scripts/setup.txt');
- // Do this all manually because our command doesn't support directories yet
- cy.openDirectory('pipelines');
- cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
- cy.openFile('complex.pipeline');
- cy.get('.common-canvas-drop-div');
- // wait an additional 300ms for the list of items to settle
- cy.wait(300);
- cy.addFileToPipeline('producer.ipynb');
- cy.addFileToPipeline('consumer.ipynb');
- cy.get('.jp-BreadCrumbs-home').click();
- cy.openDirectory('scripts');
- cy.addFileToPipeline('setup.py');
- cy.get('.jp-BreadCrumbs-home').click();
- cy.openDirectory('pipelines');
- cy.addFileToPipeline('create-source-files.py');
- cy.addFileToPipeline('producer-script.py');
- cy.get('#jp-main-dock-panel').within(() => {
- // producer props
- cy.findByText('producer.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /properties/i }).click();
- cy.get('[data-id="properties-elyra_filename"]').within(() => {
- cy.findByRole('button', { name: /browse/i }).click();
- });
- });
- cy.get('.elyra-browseFileDialog').within(() => {
- cy.openDirectory('producer.ipynb');
- });
- cy.get('#jp-main-dock-panel').within(() => {
- cy.get('[data-id="properties-elyra_outputs"]').within(() => {
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('output-1.csv');
- cy.contains('OK').click();
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('output-2.csv');
- cy.contains('OK').click();
- });
- cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
- cy.findByRole('button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- // consumer props
- cy.findByText('consumer.ipynb').click();
- cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
- cy.findByRole('button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- // setup props
- cy.findByText('setup.py').click();
- cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
- cy.findByRole('button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- cy.get('[data-id="properties-elyra_dependencies"]').within(() => {
- cy.findByRole('button', { name: /browse/i }).click();
- });
- });
- // choosing dependencies happens outside of canvas
- cy.get('.elyra-browseFileDialog').within(() => {
- cy.openDirectory('setup.txt');
- });
- // back in canvas
- cy.get('#jp-main-dock-panel').within(() => {
- // create-source-files props
- cy.findByText('create-source-files.py').click();
- cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
- cy.findByRole('button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- cy.get('[data-id="properties-elyra_outputs"]').within(() => {
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('input-1.csv');
- cy.contains('OK').click();
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('input-2.csv');
- cy.contains('OK').click();
- });
- // producer-script props
- cy.findByText('producer-script.py').click();
- cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
- cy.findByRole('button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- cy.get('[data-id="properties-elyra_outputs"]').within(() => {
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('output-3.csv');
- cy.contains('OK').click();
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('output-4.csv');
- cy.contains('OK').click();
- });
- });
- cy.savePipeline();
- cy.readFile(
- 'build/cypress-tests/pipelines/complex.pipeline'
- ).matchesSnapshot();
- });
- it('matches empty pipeline snapshot', () => {
- cy.createPipeline({ name: 'empty.pipeline' });
- cy.addFileToPipeline('helloworld.ipynb');
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('helloworld.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /delete/i }).click();
- });
- cy.savePipeline();
- cy.readFile('build/cypress-tests/empty.pipeline').matchesSnapshot();
- });
- it('matches simple pipeline snapshot', () => {
- cy.createPipeline({ name: 'simple.pipeline' });
- cy.addFileToPipeline('helloworld.ipynb');
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('helloworld.ipynb');
- });
- cy.savePipeline();
- cy.readFile('build/cypress-tests/simple.pipeline').matchesSnapshot();
- });
- it('should open notebook on double-clicking the node', () => {
- // Open a pipeline in root directory
- cy.openFile('helloworld.pipeline');
- // Open notebook node with double-click
- cy.get('.common-canvas-drop-div').within(() => {
- cy.findByText('helloworld.ipynb').dblclick();
- });
- cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');
- // close tabs
- cy.closeTab(-1); // notebook tab
- cy.closeTab(-1); // pipeline tab
- // Open a pipeline in a subfolder
- cy.bootstrapFile('pipelines/producer.ipynb');
- cy.openDirectory('pipelines');
- cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
- cy.openFile('complex.pipeline');
- cy.get('.common-canvas-drop-div');
- cy.wait(300);
- cy.addFileToPipeline('producer.ipynb');
- cy.wait(300);
- // Open notebook node with double-click
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('producer.ipynb').dblclick();
- });
- cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
- });
- it('should open notebook from node right-click menu', () => {
- // Open a pipeline in root directory
- cy.openFile('helloworld.pipeline');
- // Open notebook node with right-click menu
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('helloworld.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /open file/i }).click();
- });
- cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');
- // close tabs
- cy.closeTab(-1); // notebook tab
- cy.closeTab(-1); // pipeline tab
- // Open a pipeline in a subfolder
- cy.bootstrapFile('pipelines/producer.ipynb');
- cy.openDirectory('pipelines');
- cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
- cy.openFile('complex.pipeline');
- cy.get('.common-canvas-drop-div');
- cy.wait(300);
- cy.addFileToPipeline('producer.ipynb');
- // Open notebook node with right-click menu
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('producer.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /open file/i }).click();
- });
- cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
- });
- it('should save runtime configuration', () => {
- cy.createPipeline();
- // Create kfp runtime configuration
- cy.createRuntimeConfig({ type: 'kfp' });
- // Create airflow runtime configuration
- cy.createRuntimeConfig({ type: 'airflow' });
- // validate runtimes are now available
- cy.get('#elyra-metadata\\:runtimes').within(() => {
- cy.findByText(/kfp test runtime/i).should('exist');
- cy.findByText(/airflow test runtime/i).should('exist');
- });
- });
- it('should fail to run invalid pipeline', () => {
- // opens pipeline from the file browser
- cy.openFile('invalid.pipeline');
- // try to run invalid pipeline
- cy.findByRole('button', { name: /run pipeline/i }).click();
- cy.findByText(/failed run:/i).should('be.visible');
- });
- it('should run pipeline after adding runtime image', () => {
- cy.createPipeline();
- cy.addFileToPipeline('helloworld.ipynb'); // add Notebook
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('helloworld.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /properties/i }).click();
- // Adds runtime image to new node
- // TODO we should use the `for` attribute for the label
- cy.get('#downshift-0-toggle-button').click();
- cy.findByRole('option', { name: /anaconda/i }).click();
- });
- cy.savePipeline();
- cy.findByRole('button', { name: /run pipeline/i }).click();
- cy.findByLabelText(/pipeline name/i).should('have.value', 'untitled');
- cy.findByLabelText(/runtime platform/i).should(
- 'have.value',
- '__elyra_local__'
- );
- // execute
- cy.contains('OK').click();
- // validate job was executed successfully, this can take a while in ci
- cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
- 'be.visible'
- );
- // dismiss 'Job Succeeded' dialog
- cy.contains('OK').click();
- });
- it('should run pipeline with env vars and output files', () => {
- cy.openFile('helloworld.pipeline');
- cy.findByRole('button', { name: /run pipeline/i }).click();
- cy.findByLabelText(/pipeline name/i).should('have.value', 'helloworld');
- cy.findByLabelText(/runtime platform/i).should(
- 'have.value',
- '__elyra_local__'
- );
- // execute
- cy.contains('OK').click();
- // validate job was executed successfully, this can take a while in ci
- cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
- 'be.visible'
- );
- // dismiss 'Job Succeeded' dialog
- cy.contains('OK').click();
- cy.readFile('build/cypress-tests/output.txt').should(
- 'be.equal',
- 'TEST_ENV_1=1\nTEST_ENV_2=2\n'
- );
- });
- it('should fail to export invalid pipeline', () => {
- // Copy invalid pipeline
- cy.openFile('invalid.pipeline');
- cy.findByRole('button', { name: /export pipeline/i }).click();
- cy.findByText(/failed export:/i).should('be.visible');
- });
- it('should export pipeline as yaml', () => {
- // Install runtime configuration
- cy.installRuntimeConfig({ type: 'kfp' });
- cy.openFile('helloworld.pipeline');
- // try to export valid pipeline
- cy.findByRole('button', { name: /export pipeline/i }).click();
- // check label for generic pipeline
- cy.get('.jp-Dialog-header').contains('Export pipeline');
- cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');
- cy.findByLabelText(/runtime configuration/i)
- .select('kfp_test_runtime')
- .should('have.value', 'kfp_test_runtime');
- // Validate all export options are available
- cy.findByLabelText(/export pipeline as/i)
- .select('KFP static configuration file (YAML formatted)')
- .should('have.value', 'yaml');
- // actual export requires minio
- cy.contains('OK').click();
- // validate job was executed successfully, this can take a while in ci
- cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
- 'be.visible'
- );
- cy.readFile('build/cypress-tests/helloworld.yaml');
- });
- it('should export pipeline as python dsl', () => {
- // Install runtime configuration
- cy.installRuntimeConfig({ type: 'airflow' });
- cy.openFile('helloworld.pipeline');
- // try to export valid pipeline
- cy.findByRole('button', { name: /export pipeline/i }).click();
- // check label for generic pipeline
- cy.get('.jp-Dialog-header').contains('Export pipeline');
- cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');
- cy.findByLabelText(/runtime configuration/i)
- .select('airflow_test_runtime')
- .should('have.value', 'airflow_test_runtime');
- // overwrite existing helloworld.py file
- cy.findByLabelText(/export pipeline as/i)
- .select('Airflow domain-specific language Python code')
- .should('have.value', 'py');
- cy.findByLabelText(/replace if file already exists/i)
- .check()
- .should('be.checked');
- // actual export requires minio
- cy.contains('OK').click();
- // validate job was executed successfully, this can take a while in ci
- cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
- 'be.visible'
- );
- cy.readFile('build/cypress-tests/helloworld.py');
- });
- it('should not leak properties when switching between nodes', () => {
- cy.openFile('helloworld.pipeline');
- cy.get('#jp-main-dock-panel').within(() => {
- cy.findByText('helloworld.ipynb').rightclick();
- cy.findByRole('menuitem', { name: /properties/i }).click();
- cy.findByText('TEST_ENV_1=1').should('exist');
- cy.findByText('helloworld.py').click();
- cy.get('[data-id="properties-elyra_env_vars"]').within(() => {
- cy.findByRole('button', { name: /add item/i }).click();
- cy.focused().type('BAD=two');
- cy.contains('OK').click();
- });
- cy.findByText('BAD=two').should('exist');
- cy.findByText('helloworld.ipynb').click();
- cy.findByText('TEST_ENV_1=1').should('exist');
- cy.findByText('BAD=two').should('not.exist');
- cy.findByText('helloworld.py').click();
- cy.findByText('BAD=two').should('exist');
- });
- });
- it('kfp pipeline should display custom components', () => {
- cy.createExampleComponentCatalog({ type: 'kfp' });
- cy.createPipeline({ type: 'kfp' });
- cy.get('.palette-flyout-category[value="examples"]').click();
- const kfpCustomComponents = [
- 'elyra-kfp-examples-catalog\\:61e6f4141f65', // run notebook using papermill
- 'elyra-kfp-examples-catalog\\:737915b826e9', // filter text
- 'elyra-kfp-examples-catalog\\:a08014f9252f', // download data
- 'elyra-kfp-examples-catalog\\:d68ec7fcdf46' // calculate data hash
- ];
- kfpCustomComponents.forEach(component => {
- cy.get(`#${component}`).should('exist');
- });
- });
- it('kfp pipeline should display expected export options', () => {
- cy.createPipeline({ type: 'kfp' });
- cy.savePipeline();
- cy.installRuntimeConfig({ type: 'kfp' });
- // Validate all export options are available
- cy.findByRole('button', { name: /export pipeline/i }).click();
- cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
- cy.findByRole('option', { name: /python/i }).should('not.exist');
- // Dismiss dialog
- cy.findByRole('button', { name: /cancel/i }).click();
- });
- it('airflow pipeline should display expected export options', () => {
- cy.createPipeline({ type: 'airflow' });
- cy.savePipeline();
- cy.installRuntimeConfig({ type: 'airflow' });
- // Validate all export options are available
- cy.findByRole('button', { name: /export pipeline/i }).click();
- cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
- cy.findByRole('option', { name: /yaml/i }).should('not.exist');
- // Dismiss dialog
- cy.findByRole('button', { name: /cancel/i }).click();
- });
- it('generic pipeline should display expected export options', () => {
- cy.createPipeline();
- cy.savePipeline();
- // Test Airflow export options
- cy.installRuntimeConfig({ type: 'airflow' });
- cy.findByRole('button', { name: /export pipeline/i }).click();
- // Validate all export options are available for airflow
- cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');
- cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
- cy.findByRole('option', { name: /yaml/i }).should('not.exist');
- // Dismiss dialog
- cy.findByRole('button', { name: /cancel/i }).click();
- // Test KFP export options
- cy.installRuntimeConfig({ type: 'kfp' });
- cy.findByRole('button', { name: /export pipeline/i }).click();
- // Validate all export options are available for kfp
- cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');
- cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
- cy.findByRole('option', { name: /python/i }).should('not.exist');
- // Dismiss dialog
- cy.findByRole('button', { name: /cancel/i }).click();
- });
- it('generic pipeline toolbar should display expected runtime', () => {
- cy.createPipeline();
- cy.get('.toolbar-icon-label').contains(/runtime: generic/i);
- });
- it('kfp pipeline toolbar should display expected runtime', () => {
- cy.createPipeline({ type: 'kfp' });
- cy.get('.toolbar-icon-label').contains(/runtime: kubeflow pipelines/i);
- });
- it('airflow pipeline toolbar should display expected runtime', () => {
- cy.createPipeline({ type: 'airflow' });
- cy.get('.toolbar-icon-label').contains(/runtime: apache airflow/i);
- });
- });
- // ------------------------------
- // ----- Utility Functions
- // ------------------------------
- const checkEnabledToolbarButtons = (buttons: RegExp[]): void => {
- for (const button of buttons) {
- cy.findByRole('button', { name: button }).should('not.be.disabled');
- }
- };
- const checkDisabledToolbarButtons = (buttons: RegExp[]): void => {
- for (const button of buttons) {
- cy.findByRole('button', { name: button }).should('be.disabled');
- }
- };
|