Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,13 @@ export default defineConfig({
runMode: 1,
openMode: 1
},
defaultCommandTimeout: 8000,
defaultCommandTimeout: 10000,
execTimeout: 120000,
pageLoadTimeout: 120000,
responseTimeout: 60000,
viewportWidth: 1400,
viewportHeight: 800
viewportHeight: 800,
experimentalMemoryManagement: true
},
numTestsKeptInMemory: 10
numTestsKeptInMemory: 5
});
69 changes: 51 additions & 18 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ Cypress.Commands.add('deleteFile', (name: string): void => {
});
});

Cypress.Commands.add('deleteFiles', (patterns: string[]): void => {
if (patterns.length === 0) return;
const findArgs = patterns.map((p) => `-name "${p}"`).join(' -o ');
cy.exec(`find build/cypress/ \\( ${findArgs} \\) -delete`, {
failOnNonZeroExit: false
});
});

Cypress.Commands.add(
'createPipeline',
({ name, type, emptyPipeline } = {}): void => {
Expand All @@ -165,9 +173,7 @@ Cypress.Commands.add(
cy.openFile(name);
}

cy.get('.common-canvas-drop-div');
// wait an additional 300ms for the list of items to settle
cy.wait(300);
cy.get('.common-canvas-drop-div').should('be.visible');
}
);

Expand Down Expand Up @@ -195,9 +201,24 @@ Cypress.Commands.add('dragAndDropFileToPipeline', (name: string) => {
});

Cypress.Commands.add('savePipeline', (): void => {
cy.findByRole('button', { name: /save pipeline/i }).click();
// can take a moment to register as saved in ci
cy.wait(1000);
cy.intercept('PUT', '**/api/contents/**').as('savePipelineFile');

// Check if document has unsaved changes before clicking save
cy.document().then((doc) => {
const isDirty = doc.querySelector('.jp-Document.jp-mod-dirty') !== null;

cy.findByRole('button', { name: /save pipeline/i }).click();

if (isDirty) {
// Wait for the server to finish writing the file
cy.wait('@savePipelineFile');
}

// Confirm document is no longer dirty
cy.get('.jp-Document:not(.jp-mod-dirty)', { timeout: 10000 }).should(
'exist'
);
});
});

Cypress.Commands.add('openFile', (name: string): void => {
Expand All @@ -219,6 +240,8 @@ Cypress.Commands.add('resetJupyterLab', (): void => {
cy.findByRole('tab', { name: /file browser/i, timeout: 25000 }).should(
'exist'
);
// Wait for the launcher to be fully rendered
cy.get('.jp-Launcher', { timeout: 10000 }).should('be.visible');
});

Cypress.Commands.add('checkTabMenuOptions', (fileType: string): void => {
Expand All @@ -235,6 +258,8 @@ Cypress.Commands.add('closeTab', (index: number): void => {
});

Cypress.Commands.add('createNewScriptEditor', (language: string): void => {
// Ensure launcher is visible (may take a moment after closing a previous tab)
cy.get('.jp-Launcher', { timeout: 10000 }).should('be.visible');
cy.get(
`.jp-LauncherCard[data-category="Elyra"][title="Create a new ${language} Editor"]:visible`
).click();
Expand Down Expand Up @@ -308,16 +333,7 @@ Cypress.Commands.add(
(fileExtension: string): void => {
cy.openHelloWorld(fileExtension);
// Ensure that the file contents are as expected
cy.get('.cm-line').then((lines) => {
const content = [...lines]
.map((line) => line.innerText)
.join('\n')
.trim();
expect(content).to.equal('print("Hello Elyra")');
});

// Close the file editor
cy.closeTab(-1);
cy.get('.cm-line').should('contain.text', 'print("Hello Elyra")');
}
);

Expand Down Expand Up @@ -345,7 +361,24 @@ Cypress.Commands.add('dismissAssistant', (fileType: string): void => {
});
});

// Allowlist of known benign JupyterLab errors that should not fail tests.
// Unknown errors are allowed to propagate so real bugs surface.
const BENIGN_ERROR_PATTERNS: RegExp[] = [
/ResizeObserver loop/,
/cancelled/,
/Disposed/,
/restore\(\) must be called/,
/Non-Error promise rejection/,
// JupyterLab internal null-pointer errors from extensions
/Cannot read properties of null/
];

Cypress.on('uncaught:exception', (err, _runnable) => {
console.log('Uncaught exception:', err);
return false; // Prevent Cypress from failing the test
const message = err.message ?? String(err);
if (BENIGN_ERROR_PATTERNS.some((pattern) => pattern.test(message))) {
return false; // Suppress known benign errors
}
// Let unknown errors fail the test
console.error('Uncaught exception (not suppressed):', err);
return undefined;
});
1 change: 1 addition & 0 deletions cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ declare namespace Cypress {
type: 'kfp' | 'airflow';
}): Chainable<void>;
deleteFile(fileName: string): Chainable<void>;
deleteFiles(patterns: string[]): Chainable<void>;
openDirectory(fileName: string): Chainable<void>;
addFileToPipeline(fileName: string): Chainable<void>;
dragAndDropFileToPipeline(fileName: string): Chainable<void>;
Expand Down
57 changes: 21 additions & 36 deletions cypress/tests/codesnippet.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ describe('Code Snippet tests', () => {
it('should delete existing Code Snippet', () => {
createValidCodeSnippet(snippetName);

cy.wait(500);

getSnippetByName(snippetName);

deleteSnippet(snippetName);
Expand All @@ -120,19 +118,16 @@ describe('Code Snippet tests', () => {
// Duplicate snippet
it('should duplicate existing Code Snippet', () => {
createValidCodeSnippet(snippetName);
cy.wait(500);
let snippetRef = getSnippetByName(snippetName);
expect(snippetRef).to.not.be.null;

// create a duplicate of this snippet
duplicateSnippet(snippetName);
cy.wait(100);
snippetRef = getSnippetByName(`${snippetName}-Copy1`);
expect(snippetRef).to.not.be.null;

// create another duplicate of this snippet
duplicateSnippet(snippetName);
cy.wait(100);
snippetRef = getSnippetByName(`${snippetName}-Copy2`);
expect(snippetRef).to.not.be.null;

Expand Down Expand Up @@ -207,8 +202,6 @@ describe('Code Snippet tests', () => {
.type(newSnippetName);
saveAndCloseMetadataEditor();

cy.wait(500);

// Check new snippet name is displayed
const updatedSnippetItem = getSnippetByName(newSnippetName);

Expand All @@ -222,9 +215,6 @@ describe('Code Snippet tests', () => {
});

it('should fail to insert a code snippet into unsupported widget', () => {
// Give time for the Launcher tab to load
cy.wait(2000);

createValidCodeSnippet(snippetName);

// Insert snippet into launcher widget
Expand All @@ -233,19 +223,16 @@ describe('Code Snippet tests', () => {
// Check if insertion failed and dismiss dialog
cy.get('.jp-Dialog-header').contains('Error');
cy.get('button.jp-mod-accept').click();
cy.wait(100);
});

it('should insert a python code snippet into python editor', () => {
// Give time for the Launcher tab to load
cy.wait(2000);

createValidCodeSnippet(snippetName);

// Open blank python file
cy.createNewScriptEditor('Python');

cy.wait(1500);
// Wait for script editor to be ready
cy.get('.elyra-ScriptEditor', { timeout: 10000 }).should('be.visible');

// Insert snippet into python editor
insert(snippetName);
Expand All @@ -256,15 +243,13 @@ describe('Code Snippet tests', () => {
});

it('should fail to insert a java code snippet into python editor', () => {
// Give time for the Launcher tab to load
cy.wait(2000);

createValidCodeSnippet(snippetName, 'Java');

// Open blank python file
cy.createNewScriptEditor('Python');

cy.wait(500);
// Wait for script editor to be ready
cy.get('.elyra-ScriptEditor', { timeout: 10000 }).should('be.visible');

// Insert snippet into python editor
insert(snippetName);
Expand All @@ -287,12 +272,12 @@ describe('Code Snippet tests', () => {

saveAndCloseMetadataEditor();

cy.wait(500);

// Close Code Snippets sidebar and open File Browser
cy.get('.jp-SideBar [title*="Code Snippets"]').click();
cy.get('.jp-SideBar [title*="File Browser"]').click();
cy.wait(500);
cy.get('.jp-SideBar .lm-mod-current[title*="File Browser"]').should(
'exist'
);

// Create new notebook via File menu
cy.get('.lm-MenuBar-itemLabel:contains("File")').first().click();
Expand All @@ -303,14 +288,15 @@ describe('Code Snippet tests', () => {
cy.get('.jp-Dialog', { timeout: 10000 }).should('be.visible');
cy.get('.jp-Dialog .jp-mod-accept').click();
cy.get('.jp-Dialog').should('not.exist');
cy.wait(500);

// Check widget is loaded - Update selector for modern JupyterLab
// Check widget is loaded
cy.get('.cm-editor:visible');

// Re-open Code Snippets sidebar as it may have switched when opening notebook
cy.get('.jp-SideBar [title="Code Snippets"]').click();
cy.wait(500);
cy.get('.jp-SideBar .lm-mod-current[title*="Code Snippets"]').should(
'exist'
);

insert(snippetName);

Expand All @@ -328,27 +314,28 @@ describe('Code Snippet tests', () => {

saveAndCloseMetadataEditor();

cy.wait(500);

// Close Code Snippets sidebar and open File Browser
cy.get('.jp-SideBar [title*="Code Snippets"]').click();
cy.get('.jp-SideBar [title*="File Browser"]').click();
cy.wait(500);
cy.get('.jp-SideBar .lm-mod-current[title*="File Browser"]').should(
'exist'
);

// Create new markdown via File menu
cy.get('.lm-MenuBar-itemLabel:contains("File")').first().click();
cy.get('.lm-Menu-itemLabel:contains("New")').first().click();
cy.get(
'[data-command="fileeditor:create-new-markdown-file"] > .lm-Menu-itemLabel'
).click();
cy.wait(500);

// Check widget is loaded - Update selector for modern JupyterLab
// Check widget is loaded
cy.get('.cm-editor:visible');

// Re-open Code Snippets sidebar as it may have switched when opening markdown
cy.get('.jp-SideBar [title="Code Snippets"]').click();
cy.wait(500);
cy.get('.jp-SideBar .lm-mod-current[title*="Code Snippets"]').should(
'exist'
);

insert(snippetName);

Expand All @@ -370,7 +357,7 @@ describe('Code Snippet tests', () => {
const openCodeSnippetExtension = (): void => {
// In JupyterLab 4, click the Code Snippets tab button
cy.get('.jp-SideBar [title*="Code Snippets"]').click();
cy.get('.jp-SideBar .lm-mod-current[title*="Code Snippets"]');
cy.get('.jp-SideBar .lm-mod-current[title*="Code Snippets"]').should('exist');
};

const getSnippetByName = (
Expand Down Expand Up @@ -414,7 +401,8 @@ const createValidCodeSnippet = (

saveAndCloseMetadataEditor();

cy.wait(1000);
// Wait for snippet to appear in the list
cy.get(`[data-item-id="${snippetName}"]`).should('exist');
};

const clickCreateNewSnippetButton = (): void => {
Expand All @@ -430,7 +418,6 @@ const checkValidationWarnings = (count: number): void => {

const saveAndCloseMetadataEditor = (): void => {
cy.get('.elyra-metadataEditor-saveButton > button:visible').click();
cy.wait(500);
};

const typeCodeSnippetName = (name: string): void => {
Expand Down Expand Up @@ -463,7 +450,6 @@ const deleteSnippet = (snippetName: string): void => {
cy.get('.jp-Dialog .lm-TabBar-tabCloseIcon')
.first()
.click({ force: true });
cy.wait(500);
}
});

Expand Down Expand Up @@ -500,7 +486,6 @@ const insert = (snippetName: string): void => {
getActionButtonsElement(snippetName).within(() => {
cy.get('button[title="Insert"]').click();
});
cy.wait(500);
};

const editSnippetLanguage = (snippetName: string, lang: string): void => {
Expand Down
Loading
Loading