Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
80 changes: 45 additions & 35 deletions cypress/e2e/interface_builder.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@ const parseMappingOptions = (mapping) => {
};

const setupInterfaceEditorFromSource = (iface) => {
cy.get('#interfaceSource').clear().paste(JSON.stringify(iface));
cy.wait(500);
cy.get('.monaco-editor')
.should('be.visible')
.then(() => {
cy.window().then((win) => {
const editor = win.monaco.editor.getModels()[0];
editor.setValue(JSON.stringify(iface, null, 4));
});
});
Comment on lines +16 to +23
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be ideal to stick to the basic Cypress commands, thus simulating an actual user interacting and writing (or pasting) into the editor.
The main idea when doing e2e tests is to avoid thinking about the implementation and just test the expected user interaction.
Do you think it's feasible, or did you encounter some issues?

};

const setupInterfaceEditorFromUI = (iface) => {
Expand Down Expand Up @@ -597,6 +603,7 @@ describe('Interface builder tests', () => {
interfaceFixtures.forEach((interfaceFixture) => {
cy.fixture(interfaceFixture).then(({ data: iface }) => {
setupInterfaceEditorFromSource(iface);
setupInterfaceEditorFromUI(iface);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like cheating :)

This test correctly loads interface from its source should validate that when the user writes into the editor (setupInterfaceEditorFromSource(iface)) then the UI form fields are automatically updated and correspond to the provided source.

If we call setupInterfaceEditorFromUI(iface) to setup the UI form fields, of course they will correspond: it defies the purpose of the test.

If we want to maintain the same behavior, we can update the implementation instead of the test, making sure that when the user changes the editor's content then the UI fields are updated.

checkInterfaceEditorUIValues(iface);
});
});
Expand Down Expand Up @@ -841,6 +848,14 @@ describe('Interface builder tests', () => {
});

it('displays and saves an interface source with default values stripped out', function () {
// Continuously wait for and check the Monaco editor
function waitForEditor() {
cy.waitForMonacoEditor().then((editor) => {
const editorValue = editor.getValue();
cy.wrap(editorValue).as('editorValue');
});
}

// Case with no default values to strip out
cy.fixture('test.astarte.NoDefaultsInterface').then(({ data: iface }) => {
cy.intercept(
Expand All @@ -863,22 +878,22 @@ describe('Interface builder tests', () => {
doc: 'New documentation',
});

// Source should be displayed equal, without adding default values
cy.get('#interfaceSource')
.invoke('val')
.should((ifaceSource) => {
expect(JSON.parse(ifaceSource)).to.deep.eq(iface);
});
// Check Monaco Editor content without adding default values
cy.window().should('have.property', 'monaco');
waitForEditor();
cy.get('@editorValue').should((editorValue) => {
expect(JSON.parse(editorValue)).to.deep.eq(iface);
});

cy.get('#interfaceMinor').type(`{selectall}${newIface.version_minor}`);
cy.get('#interfaceDocumentation').clear().paste(newIface.doc);

// Source should be displayed equal, without adding default values
cy.get('#interfaceSource')
.invoke('val')
.should((ifaceSource) => {
expect(JSON.parse(ifaceSource)).to.deep.eq(newIface);
});
// Check Monaco Editor content without adding default values
cy.window().should('have.property', 'monaco');
waitForEditor();
cy.get('@editorValue').should((editorValue) => {
expect(JSON.parse(editorValue)).to.deep.eq(newIface);
});

// Interface should be saved without adding default values
cy.get('button').contains('Apply changes').scrollIntoView().click();
Expand Down Expand Up @@ -910,22 +925,22 @@ describe('Interface builder tests', () => {
doc: 'New documentation',
});

// Source should not be displayed equal, since default values are stripped out
cy.get('#interfaceSource')
.invoke('val')
.should((ifaceSource) => {
expect(JSON.parse(ifaceSource)).not.to.deep.eq(iface);
});
// Check Monaco Editor content as default values are stripped out
cy.window().should('have.property', 'monaco');
waitForEditor();
cy.get('@editorValue').should((editorValue) => {
expect(JSON.parse(editorValue)).not.to.deep.eq(iface);
});

cy.get('#interfaceMinor').type(`{selectall}${newIface.version_minor}`);
cy.get('#interfaceDocumentation').clear().paste(newIface.doc);

// Source should not be displayed equal, since default values are stripped out
cy.get('#interfaceSource')
.invoke('val')
.should((ifaceSource) => {
expect(JSON.parse(ifaceSource)).not.to.deep.eq(newIface);
});
// Check Monaco Editor content as default values are stripped out
cy.window().should('have.property', 'monaco');
waitForEditor();
cy.get('@editorValue').should((editorValue) => {
expect(JSON.parse(editorValue)).not.to.deep.eq(newIface);
});

// Interface should be saved with default values stripped out
cy.get('button').contains('Apply changes').scrollIntoView().click();
Expand Down Expand Up @@ -954,15 +969,12 @@ describe('Interface builder tests', () => {
...initialIface,
mappings: [restOfElements],
};
cy.get('#interfaceSource')
.clear()
.invoke('val', JSON.stringify(updatedIface, null, 4))
.type('{enter}');

// Set the updatedIface value in MonacoEditor using setupInterfaceEditorFromSource
setupInterfaceEditorFromSource(updatedIface);
cy.get('[data-testid="/test"]').within(() => {
// Check if the mapping endpoint is displayed
cy.contains('/test');

// Check that the Edit and Delete buttons are not present
cy.get('button').contains('Edit...').should('not.exist');
cy.get('button').contains('Delete').should('not.exist');
Expand All @@ -975,11 +987,9 @@ describe('Interface builder tests', () => {
...updatedIface,
version_minor: updatedIface.version_minor + 1,
};
cy.get('#interfaceSource')
.clear()
.invoke('val', JSON.stringify(newIface, null, 4))
.type('{enter}');

// Set the newIface value in MonacoEditor using setupInterfaceEditorFromSource
setupInterfaceEditorFromSource(newIface);
cy.intercept(
'PUT',
`/realmmanagement/v1/*/interfaces/${newIface.interface_name}/${newIface.version_major}`,
Expand Down
126 changes: 68 additions & 58 deletions cypress/e2e/trigger_builder.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const triggerOperatorToLabel = {
};

const setupTriggerEditorFromSource = (trigger) => {
cy.get('#triggerSource').scrollIntoView().paste(JSON.stringify(trigger));
cy.pasteJsonIntoEditor({ json_object: trigger });
cy.wait(1500);
};

Expand Down Expand Up @@ -81,33 +81,32 @@ const checkTriggerEditorUIValues = (trigger) => {
.contains('Any interface')
.should('be.selected');
} else {
cy.get('#triggerInterfaceName')
.scrollIntoView()
.should('be.visible')
.contains(iface)
.should('be.selected');
cy.get('#triggerInterfaceMajor')
.scrollIntoView()
.should('be.visible')
.contains(simpleTrigger.interface_major)
.should('be.selected');
cy.get('#triggerPath')
cy.window().should('have.property', 'monaco');
cy.window().should((win) => {
const models = win.monaco.editor.getModels();
expect(models).to.have.length.gt(0);
});
cy.window().then((win) => {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const editorJson = JSON.parse(editorContent);
expect(editorJson.simple_triggers[0].interface_name).to.equal(iface);
expect(editorJson.simple_triggers[0].interface_major).to.equal(simpleTrigger.interface_major);
expect(editorJson.simple_triggers[0].match_path).to.equal(simpleTrigger.match_path);
});
}
if (simpleTrigger.value_match_operator) {
cy.get('#triggerOperator')
.scrollIntoView()
.should('be.visible')
.contains(triggerOperatorToLabel[simpleTrigger.value_match_operator])
.should('be.selected');
}
if (simpleTrigger.known_value != null) {
cy.get('#triggerKnownValue')
.scrollIntoView()
.should('be.visible')
.and('have.value', simpleTrigger.match_path);
if (simpleTrigger.value_match_operator) {
cy.get('#triggerOperator')
.scrollIntoView()
.should('be.visible')
.contains(triggerOperatorToLabel[simpleTrigger.value_match_operator])
.should('be.selected');
}
if (simpleTrigger.known_value != null) {
cy.get('#triggerKnownValue')
.scrollIntoView()
.should('be.visible')
.and('have.value', simpleTrigger.known_value);
}
.and('have.value', simpleTrigger.known_value);
}
}

Expand Down Expand Up @@ -636,14 +635,16 @@ describe('Trigger builder tests', () => {
});
cy.get('table tr').contains('X-Custom-Header');
cy.get('table tr').contains('Header value');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.http_static_headers).to.deep.eq({
'X-Custom-Header': 'Header value',
});
});
}
});

// Edit http header
cy.get('table tr').contains('X-Custom-Header').parents('tr').get('i.fa-pencil-alt').click();
Expand All @@ -655,14 +656,16 @@ describe('Trigger builder tests', () => {
cy.get('button').contains('Update').click();
});
cy.get('table tr').contains('Header new value');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.http_static_headers).to.deep.eq({
'X-Custom-Header': 'Header new value',
});
});
}
});

// Delete http header
cy.get('table tr').contains('X-Custom-Header').parents('tr').get('i.fa-eraser').click();
Expand All @@ -671,13 +674,15 @@ describe('Trigger builder tests', () => {
cy.get('.modal-body').contains('Delete custom header "X-Custom-Header"?');
cy.get('button').contains('Delete').click();
});
cy.contains('X-Custom-Header').should('not.exist');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.contains('X-Custom-Header').should('not.exist');
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.http_static_headers || {}).to.deep.eq({});
});
}
});
});

it('can add, edit, remove AMQP headers', () => {
Expand All @@ -697,14 +702,16 @@ describe('Trigger builder tests', () => {
});
cy.get('table tr').contains('X-Custom-Header');
cy.get('table tr').contains('Header value');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.amqp_static_headers).to.deep.eq({
'X-Custom-Header': 'Header value',
});
});
}
});

// Edit amqp header
cy.get('table tr').contains('X-Custom-Header').parents('tr').get('i.fa-pencil-alt').click();
Expand All @@ -716,14 +723,16 @@ describe('Trigger builder tests', () => {
cy.get('button').contains('Update').click();
});
cy.get('table tr').contains('Header new value');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.amqp_static_headers).to.deep.eq({
'X-Custom-Header': 'Header new value',
});
});
}
});

// Delete amqp header
cy.get('table tr').contains('X-Custom-Header').parents('tr').get('i.fa-eraser').click();
Expand All @@ -733,12 +742,14 @@ describe('Trigger builder tests', () => {
cy.get('button').contains('Delete').click();
});
cy.contains('X-Custom-Header').should('not.exist');
cy.get('#triggerSource')
.invoke('val')
.should((triggerSource) => {
const trigger = JSON.parse(triggerSource);
cy.window().then((win) => {
if (win.monaco && win.monaco.editor) {
const editor = win.monaco.editor.getModels()[0];
const editorContent = editor.getValue();
const trigger = JSON.parse(editorContent);
expect(trigger.action.amqp_static_headers || {}).to.deep.eq({});
});
}
});
});

it('correctly loads trigger from its source', () => {
Expand Down Expand Up @@ -781,7 +792,6 @@ describe('Trigger builder tests', () => {
it('correctly shows trigger data in the Editor UI', function () {
const encodedTriggerName = encodeURIComponent(this.test_trigger.data.name);
cy.visit(`/triggers/${encodedTriggerName}/edit`);
cy.wait(1000);
cy.location('pathname').should('eq', `/triggers/${encodedTriggerName}/edit`);
checkTriggerEditorUIValues(this.test_trigger.data);
});
Expand Down
29 changes: 29 additions & 0 deletions cypress/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,32 @@ Cypress.Commands.add(
});
},
);

Cypress.Commands.add('pasteJsonIntoEditor', ({ json_object }) => {
cy.waitForMonacoEditor().then((editor) => {
editor.setValue(JSON.stringify(json_object, null, 2));
});
});

Cypress.Commands.add('waitForMonacoEditor', () => {
cy.window().should('have.property', 'monaco').then((monaco) => {
return new Cypress.Promise((resolve, reject) => {
const checkEditorInitialized = () => {
const editorModels = monaco.editor.getModels();
if (editorModels.length > 0) {
resolve(editorModels[0]);
} else {
setTimeout(checkEditorInitialized, 1000);
}
};
checkEditorInitialized();
});
});
});

Cypress.on('uncaught:exception', (err, runnable) => {
if (err.message.includes("Failed to execute 'importScripts' on 'WorkerGlobalScope'") &&
err.message.includes("monaco-editor")) {
return false;
}
});