pipeline.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. /*
  2. * Copyright 2018-2022 Elyra Authors
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. describe('Pipeline Editor tests', () => {
  17. beforeEach(() => {
  18. cy.deleteFile('helloworld.yaml');
  19. cy.deleteFile('*.pipeline'); // delete pipeline files used for testing
  20. cy.bootstrapFile('invalid.pipeline');
  21. cy.bootstrapFile('helloworld.pipeline');
  22. cy.bootstrapFile('helloworld.ipynb');
  23. cy.exec('jupyter trust build/cypress-tests/helloworld.ipynb');
  24. cy.bootstrapFile('helloworld.py');
  25. cy.bootstrapFile('helloworld.r');
  26. cy.bootstrapFile('invalid.txt');
  27. cy.resetJupyterLab();
  28. });
  29. afterEach(() => {
  30. cy.deleteFile('helloworld.ipynb'); // delete notebook file used for testing
  31. cy.deleteFile('helloworld.py'); // delete python file used for testing
  32. cy.deleteFile('output.txt'); // delete output files generated by tests
  33. cy.deleteFile('*.pipeline'); // delete pipeline files used for testing
  34. cy.deleteFile('helloworld.yaml');
  35. cy.deleteFile('invalid.txt');
  36. // delete complex test directories
  37. cy.deleteFile('pipelines');
  38. cy.deleteFile('scripts');
  39. // delete runtime configurations used for testing
  40. cy.exec('elyra-metadata remove runtimes --name=kfp_test_runtime', {
  41. failOnNonZeroExit: false
  42. });
  43. cy.exec('elyra-metadata remove runtimes --name=airflow_test_runtime', {
  44. failOnNonZeroExit: false
  45. });
  46. // delete example catalogs used for testing
  47. cy.exec(
  48. 'elyra-metadata remove component-catalogs --name=example_components',
  49. {
  50. failOnNonZeroExit: false
  51. }
  52. );
  53. });
  54. // TODO: Fix Test is actually failing
  55. // it('empty editor should have disabled buttons', () => {
  56. // cy.focusPipelineEditor();
  57. // const disabledButtons = [
  58. // '.run-action',
  59. // '.export-action',
  60. // '.clear-action',
  61. // '.undo-action',
  62. // '.redo-action',
  63. // '.cut-action',
  64. // '.copy-action',
  65. // '.paste-action',
  66. // '.deleteSelectedObjects-action',
  67. // '.arrangeHorizontally-action',
  68. // '.arrangeVertically-action'
  69. // ];
  70. // checkDisabledToolbarButtons(disabledButtons);
  71. // const enabledButtons = [
  72. // '.save-action',
  73. // '.openRuntimes-action',
  74. // '.createAutoComment-action'
  75. // ];
  76. // checkEnabledToolbarButtons(enabledButtons);
  77. // closePipelineEditor();
  78. // });
  79. it('should block unsupported files', () => {
  80. cy.createPipeline();
  81. cy.dragAndDropFileToPipeline('invalid.txt');
  82. // check for unsupported files dialog message
  83. cy.findByText(/unsupported file/i).should('be.visible');
  84. // dismiss dialog
  85. cy.contains('OK').click();
  86. });
  87. it('populated editor should have enabled buttons', () => {
  88. cy.createPipeline();
  89. cy.checkTabMenuOptions('Pipeline');
  90. cy.addFileToPipeline('helloworld.ipynb'); // add Notebook
  91. cy.addFileToPipeline('helloworld.py'); // add Python Script
  92. cy.addFileToPipeline('helloworld.r'); // add R Script
  93. // check buttons
  94. const disabledButtons = [/redo/i, /cut/i, /copy/i, /paste/i, /delete/i];
  95. checkDisabledToolbarButtons(disabledButtons);
  96. const enabledButtons = [
  97. /run pipeline/i,
  98. /save pipeline/i,
  99. /export pipeline/i,
  100. /clear/i,
  101. /open runtimes/i,
  102. /open runtime images/i,
  103. /open component catalogs/i,
  104. /undo/i,
  105. /add comment/i,
  106. /arrange horizontally/i,
  107. /arrange vertically/i
  108. ];
  109. checkEnabledToolbarButtons(enabledButtons);
  110. });
  111. it('matches complex pipeline snapshot', () => {
  112. cy.bootstrapFile('pipelines/consumer.ipynb');
  113. cy.bootstrapFile('pipelines/create-source-files.py');
  114. cy.bootstrapFile('pipelines/producer-script.py');
  115. cy.bootstrapFile('pipelines/producer.ipynb');
  116. cy.bootstrapFile('scripts/setup.py');
  117. cy.bootstrapFile('scripts/setup.txt');
  118. // Do this all manually because our command doesn't support directories yet
  119. cy.openDirectory('pipelines');
  120. cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
  121. cy.openFile('complex.pipeline');
  122. cy.get('.common-canvas-drop-div');
  123. // wait an additional 300ms for the list of items to settle
  124. cy.wait(300);
  125. cy.addFileToPipeline('producer.ipynb');
  126. cy.addFileToPipeline('consumer.ipynb');
  127. cy.get('.jp-BreadCrumbs-home').click();
  128. cy.openDirectory('scripts');
  129. cy.addFileToPipeline('setup.py');
  130. cy.get('.jp-BreadCrumbs-home').click();
  131. cy.openDirectory('pipelines');
  132. cy.addFileToPipeline('create-source-files.py');
  133. cy.addFileToPipeline('producer-script.py');
  134. cy.get('#jp-main-dock-panel').within(() => {
  135. // producer props
  136. cy.findByText('producer.ipynb').rightclick();
  137. cy.findByRole('menuitem', { name: /properties/i }).click();
  138. cy.get('[data-id="properties-elyra_filename"]').within(() => {
  139. cy.findByRole('button', { name: /browse/i }).click();
  140. });
  141. });
  142. cy.get('.elyra-browseFileDialog').within(() => {
  143. cy.openDirectory('producer.ipynb');
  144. });
  145. cy.get('#jp-main-dock-panel').within(() => {
  146. cy.get('[data-id="properties-elyra_outputs"]').within(() => {
  147. cy.findByRole('button', { name: /add item/i }).click();
  148. cy.focused().type('output-1.csv');
  149. cy.contains('OK').click();
  150. cy.findByRole('button', { name: /add item/i }).click();
  151. cy.focused().type('output-2.csv');
  152. cy.contains('OK').click();
  153. });
  154. cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
  155. cy.findByRole('button').click();
  156. cy.findByRole('option', { name: /anaconda/i }).click();
  157. });
  158. // consumer props
  159. cy.findByText('consumer.ipynb').click();
  160. cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
  161. cy.findByRole('button').click();
  162. cy.findByRole('option', { name: /anaconda/i }).click();
  163. });
  164. // setup props
  165. cy.findByText('setup.py').click();
  166. cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
  167. cy.findByRole('button').click();
  168. cy.findByRole('option', { name: /anaconda/i }).click();
  169. });
  170. cy.get('[data-id="properties-elyra_dependencies"]').within(() => {
  171. cy.findByRole('button', { name: /browse/i }).click();
  172. });
  173. });
  174. // choosing dependencies happens outside of canvas
  175. cy.get('.elyra-browseFileDialog').within(() => {
  176. cy.openDirectory('setup.txt');
  177. });
  178. // back in canvas
  179. cy.get('#jp-main-dock-panel').within(() => {
  180. // create-source-files props
  181. cy.findByText('create-source-files.py').click();
  182. cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
  183. cy.findByRole('button').click();
  184. cy.findByRole('option', { name: /anaconda/i }).click();
  185. });
  186. cy.get('[data-id="properties-elyra_outputs"]').within(() => {
  187. cy.findByRole('button', { name: /add item/i }).click();
  188. cy.focused().type('input-1.csv');
  189. cy.contains('OK').click();
  190. cy.findByRole('button', { name: /add item/i }).click();
  191. cy.focused().type('input-2.csv');
  192. cy.contains('OK').click();
  193. });
  194. // producer-script props
  195. cy.findByText('producer-script.py').click();
  196. cy.get('[data-id="properties-elyra_runtime_image"]').within(() => {
  197. cy.findByRole('button').click();
  198. cy.findByRole('option', { name: /anaconda/i }).click();
  199. });
  200. cy.get('[data-id="properties-elyra_outputs"]').within(() => {
  201. cy.findByRole('button', { name: /add item/i }).click();
  202. cy.focused().type('output-3.csv');
  203. cy.contains('OK').click();
  204. cy.findByRole('button', { name: /add item/i }).click();
  205. cy.focused().type('output-4.csv');
  206. cy.contains('OK').click();
  207. });
  208. });
  209. cy.savePipeline();
  210. cy.readFile(
  211. 'build/cypress-tests/pipelines/complex.pipeline'
  212. ).matchesSnapshot();
  213. });
  214. it('matches empty pipeline snapshot', () => {
  215. cy.createPipeline({ name: 'empty.pipeline' });
  216. cy.addFileToPipeline('helloworld.ipynb');
  217. cy.get('#jp-main-dock-panel').within(() => {
  218. cy.findByText('helloworld.ipynb').rightclick();
  219. cy.findByRole('menuitem', { name: /delete/i }).click();
  220. });
  221. cy.savePipeline();
  222. cy.readFile('build/cypress-tests/empty.pipeline').matchesSnapshot();
  223. });
  224. it('matches simple pipeline snapshot', () => {
  225. cy.createPipeline({ name: 'simple.pipeline' });
  226. cy.addFileToPipeline('helloworld.ipynb');
  227. cy.get('#jp-main-dock-panel').within(() => {
  228. cy.findByText('helloworld.ipynb');
  229. });
  230. cy.savePipeline();
  231. cy.readFile('build/cypress-tests/simple.pipeline').matchesSnapshot();
  232. });
  233. it('should open notebook on double-clicking the node', () => {
  234. // Open a pipeline in root directory
  235. cy.openFile('helloworld.pipeline');
  236. // Open notebook node with double-click
  237. cy.get('.common-canvas-drop-div').within(() => {
  238. cy.findByText('helloworld.ipynb').dblclick();
  239. });
  240. cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');
  241. // close tabs
  242. cy.closeTab(-1); // notebook tab
  243. cy.closeTab(-1); // pipeline tab
  244. // Open a pipeline in a subfolder
  245. cy.bootstrapFile('pipelines/producer.ipynb');
  246. cy.openDirectory('pipelines');
  247. cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
  248. cy.openFile('complex.pipeline');
  249. cy.get('.common-canvas-drop-div');
  250. cy.wait(300);
  251. cy.addFileToPipeline('producer.ipynb');
  252. cy.wait(300);
  253. // Open notebook node with double-click
  254. cy.get('#jp-main-dock-panel').within(() => {
  255. cy.findByText('producer.ipynb').dblclick();
  256. });
  257. cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
  258. });
  259. it('should open notebook from node right-click menu', () => {
  260. // Open a pipeline in root directory
  261. cy.openFile('helloworld.pipeline');
  262. // Open notebook node with right-click menu
  263. cy.get('#jp-main-dock-panel').within(() => {
  264. cy.findByText('helloworld.ipynb').rightclick();
  265. cy.findByRole('menuitem', { name: /open file/i }).click();
  266. });
  267. cy.findAllByRole('tab', { name: 'helloworld.ipynb' }).should('exist');
  268. // close tabs
  269. cy.closeTab(-1); // notebook tab
  270. cy.closeTab(-1); // pipeline tab
  271. // Open a pipeline in a subfolder
  272. cy.bootstrapFile('pipelines/producer.ipynb');
  273. cy.openDirectory('pipelines');
  274. cy.writeFile('build/cypress-tests/pipelines/complex.pipeline', '');
  275. cy.openFile('complex.pipeline');
  276. cy.get('.common-canvas-drop-div');
  277. cy.wait(300);
  278. cy.addFileToPipeline('producer.ipynb');
  279. // Open notebook node with right-click menu
  280. cy.get('#jp-main-dock-panel').within(() => {
  281. cy.findByText('producer.ipynb').rightclick();
  282. cy.findByRole('menuitem', { name: /open file/i }).click();
  283. });
  284. cy.findAllByRole('tab', { name: 'producer.ipynb' }).should('exist');
  285. });
  286. it('should save runtime configuration', () => {
  287. cy.createPipeline();
  288. // Create kfp runtime configuration
  289. cy.createRuntimeConfig({ type: 'kfp' });
  290. // Create airflow runtime configuration
  291. cy.createRuntimeConfig({ type: 'airflow' });
  292. // validate runtimes are now available
  293. cy.get('#elyra-metadata\\:runtimes').within(() => {
  294. cy.findByText(/kfp test runtime/i).should('exist');
  295. cy.findByText(/airflow test runtime/i).should('exist');
  296. });
  297. });
  298. it('should fail to run invalid pipeline', () => {
  299. // opens pipeline from the file browser
  300. cy.openFile('invalid.pipeline');
  301. // try to run invalid pipeline
  302. cy.findByRole('button', { name: /run pipeline/i }).click();
  303. cy.findByText(/failed run:/i).should('be.visible');
  304. });
  305. it('should run pipeline after adding runtime image', () => {
  306. cy.createPipeline();
  307. cy.addFileToPipeline('helloworld.ipynb'); // add Notebook
  308. cy.get('#jp-main-dock-panel').within(() => {
  309. cy.findByText('helloworld.ipynb').rightclick();
  310. cy.findByRole('menuitem', { name: /properties/i }).click();
  311. // Adds runtime image to new node
  312. // TODO we should use the `for` attribute for the label
  313. cy.get('#downshift-0-toggle-button').click();
  314. cy.findByRole('option', { name: /anaconda/i }).click();
  315. });
  316. cy.savePipeline();
  317. cy.findByRole('button', { name: /run pipeline/i }).click();
  318. cy.findByLabelText(/pipeline name/i).should('have.value', 'untitled');
  319. cy.findByLabelText(/runtime platform/i).should(
  320. 'have.value',
  321. '__elyra_local__'
  322. );
  323. // execute
  324. cy.contains('OK').click();
  325. // validate job was executed successfully, this can take a while in ci
  326. cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
  327. 'be.visible'
  328. );
  329. // dismiss 'Job Succeeded' dialog
  330. cy.contains('OK').click();
  331. });
  332. it('should run pipeline with env vars and output files', () => {
  333. cy.openFile('helloworld.pipeline');
  334. cy.findByRole('button', { name: /run pipeline/i }).click();
  335. cy.findByLabelText(/pipeline name/i).should('have.value', 'helloworld');
  336. cy.findByLabelText(/runtime platform/i).should(
  337. 'have.value',
  338. '__elyra_local__'
  339. );
  340. // execute
  341. cy.contains('OK').click();
  342. // validate job was executed successfully, this can take a while in ci
  343. cy.findByText(/job execution succeeded/i, { timeout: 30000 }).should(
  344. 'be.visible'
  345. );
  346. // dismiss 'Job Succeeded' dialog
  347. cy.contains('OK').click();
  348. cy.readFile('build/cypress-tests/output.txt').should(
  349. 'be.equal',
  350. 'TEST_ENV_1=1\nTEST_ENV_2=2\n'
  351. );
  352. });
  353. it('should fail to export invalid pipeline', () => {
  354. // Copy invalid pipeline
  355. cy.openFile('invalid.pipeline');
  356. cy.findByRole('button', { name: /export pipeline/i }).click();
  357. cy.findByText(/failed export:/i).should('be.visible');
  358. });
  359. it('should export pipeline as yaml', () => {
  360. // Install runtime configuration
  361. cy.installRuntimeConfig({ type: 'kfp' });
  362. cy.openFile('helloworld.pipeline');
  363. // try to export valid pipeline
  364. cy.findByRole('button', { name: /export pipeline/i }).click();
  365. // check label for generic pipeline
  366. cy.get('.jp-Dialog-header').contains('Export pipeline');
  367. cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');
  368. cy.findByLabelText(/runtime configuration/i)
  369. .select('kfp_test_runtime')
  370. .should('have.value', 'kfp_test_runtime');
  371. // Validate all export options are available
  372. cy.findByLabelText(/export pipeline as/i)
  373. .select('KFP static configuration file (YAML formatted)')
  374. .should('have.value', 'yaml');
  375. // actual export requires minio
  376. cy.contains('OK').click();
  377. // validate job was executed successfully, this can take a while in ci
  378. cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
  379. 'be.visible'
  380. );
  381. cy.readFile('build/cypress-tests/helloworld.yaml');
  382. });
  383. it('should export pipeline as python dsl', () => {
  384. // Install runtime configuration
  385. cy.installRuntimeConfig({ type: 'airflow' });
  386. cy.openFile('helloworld.pipeline');
  387. // try to export valid pipeline
  388. cy.findByRole('button', { name: /export pipeline/i }).click();
  389. // check label for generic pipeline
  390. cy.get('.jp-Dialog-header').contains('Export pipeline');
  391. cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');
  392. cy.findByLabelText(/runtime configuration/i)
  393. .select('airflow_test_runtime')
  394. .should('have.value', 'airflow_test_runtime');
  395. // overwrite existing helloworld.py file
  396. cy.findByLabelText(/export pipeline as/i)
  397. .select('Airflow domain-specific language Python code')
  398. .should('have.value', 'py');
  399. cy.findByLabelText(/replace if file already exists/i)
  400. .check()
  401. .should('be.checked');
  402. // actual export requires minio
  403. cy.contains('OK').click();
  404. // validate job was executed successfully, this can take a while in ci
  405. cy.findByText(/pipeline export succeeded/i, { timeout: 30000 }).should(
  406. 'be.visible'
  407. );
  408. cy.readFile('build/cypress-tests/helloworld.py');
  409. });
  410. it('should not leak properties when switching between nodes', () => {
  411. cy.openFile('helloworld.pipeline');
  412. cy.get('#jp-main-dock-panel').within(() => {
  413. cy.findByText('helloworld.ipynb').rightclick();
  414. cy.findByRole('menuitem', { name: /properties/i }).click();
  415. cy.findByText('TEST_ENV_1=1').should('exist');
  416. cy.findByText('helloworld.py').click();
  417. cy.get('[data-id="properties-elyra_env_vars"]').within(() => {
  418. cy.findByRole('button', { name: /add item/i }).click();
  419. cy.focused().type('BAD=two');
  420. cy.contains('OK').click();
  421. });
  422. cy.findByText('BAD=two').should('exist');
  423. cy.findByText('helloworld.ipynb').click();
  424. cy.findByText('TEST_ENV_1=1').should('exist');
  425. cy.findByText('BAD=two').should('not.exist');
  426. cy.findByText('helloworld.py').click();
  427. cy.findByText('BAD=two').should('exist');
  428. });
  429. });
  430. it('kfp pipeline should display custom components', () => {
  431. cy.createExampleComponentCatalog({ type: 'kfp' });
  432. cy.createPipeline({ type: 'kfp' });
  433. cy.get('.palette-flyout-category[value="examples"]').click();
  434. const kfpCustomComponents = [
  435. 'elyra-kfp-examples-catalog\\:61e6f4141f65', // run notebook using papermill
  436. 'elyra-kfp-examples-catalog\\:737915b826e9', // filter text
  437. 'elyra-kfp-examples-catalog\\:a08014f9252f', // download data
  438. 'elyra-kfp-examples-catalog\\:d68ec7fcdf46' // calculate data hash
  439. ];
  440. kfpCustomComponents.forEach(component => {
  441. cy.get(`#${component}`).should('exist');
  442. });
  443. });
  444. it('kfp pipeline should display expected export options', () => {
  445. cy.createPipeline({ type: 'kfp' });
  446. cy.savePipeline();
  447. cy.installRuntimeConfig({ type: 'kfp' });
  448. // Validate all export options are available
  449. cy.findByRole('button', { name: /export pipeline/i }).click();
  450. cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
  451. cy.findByRole('option', { name: /python/i }).should('not.exist');
  452. // Dismiss dialog
  453. cy.findByRole('button', { name: /cancel/i }).click();
  454. });
  455. it('airflow pipeline should display expected export options', () => {
  456. cy.createPipeline({ type: 'airflow' });
  457. cy.savePipeline();
  458. cy.installRuntimeConfig({ type: 'airflow' });
  459. // Validate all export options are available
  460. cy.findByRole('button', { name: /export pipeline/i }).click();
  461. cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
  462. cy.findByRole('option', { name: /yaml/i }).should('not.exist');
  463. // Dismiss dialog
  464. cy.findByRole('button', { name: /cancel/i }).click();
  465. });
  466. it('generic pipeline should display expected export options', () => {
  467. cy.createPipeline();
  468. cy.savePipeline();
  469. // Test Airflow export options
  470. cy.installRuntimeConfig({ type: 'airflow' });
  471. cy.findByRole('button', { name: /export pipeline/i }).click();
  472. // Validate all export options are available for airflow
  473. cy.findByLabelText(/runtime platform/i).select('APACHE_AIRFLOW');
  474. cy.findByRole('option', { name: /python/i }).should('have.value', 'py');
  475. cy.findByRole('option', { name: /yaml/i }).should('not.exist');
  476. // Dismiss dialog
  477. cy.findByRole('button', { name: /cancel/i }).click();
  478. // Test KFP export options
  479. cy.installRuntimeConfig({ type: 'kfp' });
  480. cy.findByRole('button', { name: /export pipeline/i }).click();
  481. // Validate all export options are available for kfp
  482. cy.findByLabelText(/runtime platform/i).select('KUBEFLOW_PIPELINES');
  483. cy.findByRole('option', { name: /yaml/i }).should('have.value', 'yaml');
  484. cy.findByRole('option', { name: /python/i }).should('not.exist');
  485. // Dismiss dialog
  486. cy.findByRole('button', { name: /cancel/i }).click();
  487. });
  488. it('generic pipeline toolbar should display expected runtime', () => {
  489. cy.createPipeline();
  490. cy.get('.toolbar-icon-label').contains(/runtime: generic/i);
  491. });
  492. it('kfp pipeline toolbar should display expected runtime', () => {
  493. cy.createPipeline({ type: 'kfp' });
  494. cy.get('.toolbar-icon-label').contains(/runtime: kubeflow pipelines/i);
  495. });
  496. it('airflow pipeline toolbar should display expected runtime', () => {
  497. cy.createPipeline({ type: 'airflow' });
  498. cy.get('.toolbar-icon-label').contains(/runtime: apache airflow/i);
  499. });
  500. });
  501. // ------------------------------
  502. // ----- Utility Functions
  503. // ------------------------------
  504. const checkEnabledToolbarButtons = (buttons: RegExp[]): void => {
  505. for (const button of buttons) {
  506. cy.findByRole('button', { name: button }).should('not.be.disabled');
  507. }
  508. };
  509. const checkDisabledToolbarButtons = (buttons: RegExp[]): void => {
  510. for (const button of buttons) {
  511. cy.findByRole('button', { name: button }).should('be.disabled');
  512. }
  513. };