mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-13 05:47:31 -08:00
feat: Add credentials E2E test suite and page object (#4596)
* fix: Fix inferred type of X cannot be named error after pnpm update * feat: Change page objects to expose actions and getters. Add credential creation suite
This commit is contained in:
parent
772ec78349
commit
b5b44d1b59
|
@ -5,7 +5,7 @@ module.exports = defineConfig({
|
||||||
e2e: {
|
e2e: {
|
||||||
baseUrl: 'http://localhost:5678',
|
baseUrl: 'http://localhost:5678',
|
||||||
video: false,
|
video: false,
|
||||||
screenshotOnRunFailure: false,
|
screenshotOnRunFailure: true,
|
||||||
experimentalSessionAndOrigin: true,
|
experimentalSessionAndOrigin: true,
|
||||||
experimentalInteractiveRunEvents: true,
|
experimentalInteractiveRunEvents: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ const password = DEFAULT_USER_PASSWORD;
|
||||||
const firstName = randFirstName();
|
const firstName = randFirstName();
|
||||||
const lastName = randLastName();
|
const lastName = randLastName();
|
||||||
|
|
||||||
describe('Authentication flow', () => {
|
describe('Authentication', () => {
|
||||||
it('should sign user up', () => {
|
it('should sign user up', () => {
|
||||||
cy.signup(username, firstName, lastName, password);
|
cy.signup(username, firstName, lastName, password);
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ const lastName = randLastName();
|
||||||
const WorkflowsPage = new WorkflowsPageClass();
|
const WorkflowsPage = new WorkflowsPageClass();
|
||||||
const WorkflowPage = new WorkflowPageClass();
|
const WorkflowPage = new WorkflowPageClass();
|
||||||
|
|
||||||
describe('Workflows flow', () => {
|
describe('Workflows', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.signup(username, firstName, lastName, password);
|
cy.signup(username, firstName, lastName, password);
|
||||||
|
|
||||||
|
@ -26,59 +26,61 @@ describe('Workflows flow', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new workflow using empty state card', () => {
|
it('should create a new workflow using empty state card', () => {
|
||||||
WorkflowsPage.get('newWorkflowButtonCard').should('be.visible');
|
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||||
WorkflowsPage.get('newWorkflowButtonCard').click();
|
WorkflowsPage.getters.newWorkflowButtonCard().click();
|
||||||
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_1.json', `Empty State Card Workflow ${uuid()}`);
|
cy.createFixtureWorkflow('Test_workflow_1.json', `Empty State Card Workflow ${uuid()}`);
|
||||||
|
|
||||||
WorkflowPage.get('workflowTags').should('contain.text', 'some-tag-1');
|
WorkflowPage.getters.workflowTags().should('contain.text', 'some-tag-1');
|
||||||
WorkflowPage.get('workflowTags').should('contain.text', 'some-tag-2');
|
WorkflowPage.getters.workflowTags().should('contain.text', 'some-tag-2');
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should create a new workflow using add workflow button', () => {
|
it('should create a new workflow using add workflow button', () => {
|
||||||
WorkflowsPage.get('newWorkflowButtonCard').should('not.exist');
|
WorkflowsPage.getters.newWorkflowButtonCard().should('not.exist');
|
||||||
WorkflowsPage.get('createWorkflowButton').click();
|
WorkflowsPage.getters.createWorkflowButton().click();
|
||||||
|
|
||||||
cy.createFixtureWorkflow('Test_workflow_2.json', `Add Workflow Button Workflow ${uuid()}`);
|
cy.createFixtureWorkflow('Test_workflow_2.json', `Add Workflow Button Workflow ${uuid()}`);
|
||||||
|
|
||||||
WorkflowPage.get('workflowTags').should('contain.text', 'other-tag-1');
|
WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-1');
|
||||||
WorkflowPage.get('workflowTags').should('contain.text', 'other-tag-2');
|
WorkflowPage.getters.workflowTags().should('contain.text', 'other-tag-2');
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should search for a workflow', () => {
|
it('should search for a workflow', () => {
|
||||||
WorkflowsPage.get('searchBar').type('Empty State Card Workflow');
|
WorkflowsPage.getters.searchBar().type('Empty State Card Workflow');
|
||||||
|
|
||||||
WorkflowsPage.get('workflowCards').should('have.length', 1);
|
WorkflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
WorkflowsPage.get('workflowCard', 'Empty State Card Workflow').should('contain.text', 'Empty State Card Workflow');
|
WorkflowsPage.getters.workflowCard('Empty State Card Workflow').should('contain.text', 'Empty State Card Workflow');
|
||||||
|
|
||||||
WorkflowsPage.get('searchBar').clear().type('Add Workflow Button Workflow');
|
WorkflowsPage.getters.searchBar().clear().type('Add Workflow Button Workflow');
|
||||||
|
|
||||||
WorkflowsPage.get('workflowCards').should('have.length', 1);
|
WorkflowsPage.getters.workflowCards().should('have.length', 1);
|
||||||
WorkflowsPage.get('workflowCard', 'Add Workflow Button Workflow').should('contain.text', 'Add Workflow Button Workflow');
|
WorkflowsPage.getters.workflowCard('Add Workflow Button Workflow').should('contain.text', 'Add Workflow Button Workflow');
|
||||||
|
|
||||||
|
WorkflowsPage.getters.searchBar().clear().type('Some non-existent workflow');
|
||||||
|
WorkflowsPage.getters.workflowCards().should('not.exist');
|
||||||
|
|
||||||
WorkflowsPage.get('searchBar').clear().type('Some non-existent workflow');
|
|
||||||
WorkflowsPage.get('workflowCards').should('not.exist');
|
|
||||||
cy.contains('No workflows found').should('be.visible');
|
cy.contains('No workflows found').should('be.visible');
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should delete all the workflows', () => {
|
it('should delete all the workflows', () => {
|
||||||
WorkflowsPage.get('workflowCards').should('have.length', 2);
|
WorkflowsPage.getters.workflowCards().should('have.length', 2);
|
||||||
|
|
||||||
WorkflowsPage.get('workflowCards').each(($el) => {
|
WorkflowsPage.getters.workflowCards().each(($el) => {
|
||||||
const workflowName = $el.find('[data-test-id="workflow-card-name"]').text();
|
const workflowName = $el.find('[data-test-id="workflow-card-name"]').text();
|
||||||
|
|
||||||
WorkflowsPage.get('workflowCardActions', workflowName).click();
|
WorkflowsPage.getters.workflowCardActions(workflowName).click();
|
||||||
WorkflowsPage.get('workflowDeleteButton').click();
|
WorkflowsPage.getters.workflowDeleteButton().click();
|
||||||
|
|
||||||
cy.get('button').contains('delete').click();
|
cy.get('button').contains('delete').click();
|
||||||
})
|
})
|
||||||
|
|
||||||
WorkflowsPage.get('newWorkflowButtonCard').should('be.visible');
|
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||||
WorkflowsPage.get('newWorkflowTemplateCard').should('be.visible');
|
WorkflowsPage.getters.newWorkflowTemplateCard().should('be.visible');
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should contain empty state cards', () => {
|
it('should contain empty state cards', () => {
|
||||||
WorkflowsPage.get('newWorkflowButtonCard').should('be.visible');
|
WorkflowsPage.getters.newWorkflowButtonCard().should('be.visible');
|
||||||
WorkflowsPage.get('newWorkflowTemplateCard').should('be.visible');
|
WorkflowsPage.getters.newWorkflowTemplateCard().should('be.visible');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
41
cypress/e2e/2-credentials.cy.ts
Normal file
41
cypress/e2e/2-credentials.cy.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { DEFAULT_USER_EMAIL, DEFAULT_USER_PASSWORD } from "../constants";
|
||||||
|
import { randFirstName, randLastName } from "@ngneat/falso";
|
||||||
|
import { CredentialsPage, CredentialsModal } from '../pages';
|
||||||
|
// import { v4 as uuid } from 'uuid';
|
||||||
|
|
||||||
|
const username = DEFAULT_USER_EMAIL;
|
||||||
|
const password = DEFAULT_USER_PASSWORD;
|
||||||
|
const firstName = randFirstName();
|
||||||
|
const lastName = randLastName();
|
||||||
|
const credentialsPage = new CredentialsPage();
|
||||||
|
const credentialsModal = new CredentialsModal();
|
||||||
|
|
||||||
|
describe('Credentials', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.signup(username, firstName, lastName, password);
|
||||||
|
|
||||||
|
cy.on('uncaught:exception', (err, runnable) => {
|
||||||
|
expect(err.message).to.include('Not logged in');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.signin(username, password);
|
||||||
|
cy.visit(credentialsPage.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a new credential using empty state', () => {
|
||||||
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
|
|
||||||
|
credentialsModal.getters.newCredentialModal().should('be.visible');
|
||||||
|
credentialsModal.getters.newCredentialTypeSelect().should('be.visible');
|
||||||
|
credentialsModal.getters.newCredentialTypeOption('Notion API').click();
|
||||||
|
|
||||||
|
credentialsModal.getters.newCredentialTypeButton().click();
|
||||||
|
|
||||||
|
credentialsModal.getters.connectionParameter('API Key').type('1234567890');
|
||||||
|
|
||||||
|
credentialsModal.actions.setName('My awesome Notion account');
|
||||||
|
credentialsModal.actions.save();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,15 +1,6 @@
|
||||||
import { IE2ETestPage, IE2ETestPageElement } from "../types";
|
import { IE2ETestPage, IE2ETestPageElement } from "../types";
|
||||||
|
|
||||||
|
|
||||||
export class BasePage implements IE2ETestPage {
|
export class BasePage implements IE2ETestPage {
|
||||||
elements: Record<string, IE2ETestPageElement> = {};
|
getters: Record<string, IE2ETestPageElement> = {};
|
||||||
get(id: keyof BasePage['elements'], ...args: unknown[]): ReturnType<IE2ETestPageElement> {
|
actions: Record<string, (...args: any[]) => void> = {};
|
||||||
const getter = this.elements[id];
|
|
||||||
|
|
||||||
if (!getter) {
|
|
||||||
throw new Error(`No element with id "${id}" found. Check your page object definition.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getter(...args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
17
cypress/pages/credentials.ts
Normal file
17
cypress/pages/credentials.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { BasePage } from "./base";
|
||||||
|
|
||||||
|
export class CredentialsPage extends BasePage {
|
||||||
|
url = '/credentials';
|
||||||
|
getters = {
|
||||||
|
emptyListCreateCredentialButton: () => cy.getByTestId('empty-resources-list').find('button'),
|
||||||
|
createCredentialButton: () => cy.getByTestId('resources-list-add'),
|
||||||
|
searchBar: () => cy.getByTestId('resources-list-search'),
|
||||||
|
credentialCards: () => cy.getByTestId('credential-card'),
|
||||||
|
credentialCard: (credentialName: string) => cy.getByTestId('credential-card')
|
||||||
|
.contains(credentialName)
|
||||||
|
.parents('[data-test-id="credential-card"]'),
|
||||||
|
credentialCardActions: (credentialName: string) => this.getters.credentialCard(credentialName)
|
||||||
|
.findChildByTestId('credential-card-actions'),
|
||||||
|
credentialDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete')
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
export * from './base';
|
export * from './base';
|
||||||
|
export * from './credentials';
|
||||||
export * from './signin';
|
export * from './signin';
|
||||||
export * from './signup';
|
export * from './signup';
|
||||||
export * from './workflows';
|
export * from './workflows';
|
||||||
|
export * from './modals';
|
||||||
|
|
26
cypress/pages/modals/credentials-modal.ts
Normal file
26
cypress/pages/modals/credentials-modal.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { BasePage } from "../base";
|
||||||
|
|
||||||
|
export class CredentialsModal extends BasePage {
|
||||||
|
getters = {
|
||||||
|
newCredentialModal: () => cy.getByTestId('selectCredential-modal', { timeout: 5000 }),
|
||||||
|
newCredentialTypeSelect: () => cy.getByTestId('new-credential-type-select'),
|
||||||
|
newCredentialTypeOption: (credentialType: string) => cy.getByTestId('new-credential-type-select-option').contains(credentialType),
|
||||||
|
newCredentialTypeButton: () => cy.getByTestId('new-credential-type-button'),
|
||||||
|
connectionParameters: () => cy.getByTestId('credential-connection-parameter'),
|
||||||
|
connectionParameter: (fieldName: string) => this.getters.connectionParameters().contains(fieldName)
|
||||||
|
.parents('[data-test-id="credential-connection-parameter"]')
|
||||||
|
.find('.n8n-input input'),
|
||||||
|
name: () => cy.getByTestId('credential-name'),
|
||||||
|
nameInput: () => cy.getByTestId('credential-name').find('input'),
|
||||||
|
saveButton: () => cy.getByTestId('credential-save-button')
|
||||||
|
};
|
||||||
|
actions = {
|
||||||
|
setName: (name: string) => {
|
||||||
|
this.getters.name().click();
|
||||||
|
this.getters.nameInput().clear().type(name);
|
||||||
|
},
|
||||||
|
save: () => {
|
||||||
|
this.getters.saveButton().click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
1
cypress/pages/modals/index.ts
Normal file
1
cypress/pages/modals/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from './credentials-modal';
|
|
@ -2,7 +2,7 @@ import { BasePage } from "./base";
|
||||||
|
|
||||||
export class SigninPage extends BasePage {
|
export class SigninPage extends BasePage {
|
||||||
url = '/signin';
|
url = '/signin';
|
||||||
elements = {
|
getters = {
|
||||||
form: () => cy.getByTestId('auth-form'),
|
form: () => cy.getByTestId('auth-form'),
|
||||||
email: () => cy.getByTestId('email'),
|
email: () => cy.getByTestId('email'),
|
||||||
password: () => cy.getByTestId('password'),
|
password: () => cy.getByTestId('password'),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BasePage } from "./base";
|
||||||
|
|
||||||
export class SignupPage extends BasePage {
|
export class SignupPage extends BasePage {
|
||||||
url = '/setup';
|
url = '/setup';
|
||||||
elements = {
|
getters = {
|
||||||
form: () => cy.getByTestId('auth-form'),
|
form: () => cy.getByTestId('auth-form'),
|
||||||
email: () => cy.getByTestId('email'),
|
email: () => cy.getByTestId('email'),
|
||||||
firstName: () => cy.getByTestId('firstName'),
|
firstName: () => cy.getByTestId('firstName'),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BasePage } from "./base";
|
||||||
|
|
||||||
export class WorkflowPage extends BasePage {
|
export class WorkflowPage extends BasePage {
|
||||||
url = '/workflow/new';
|
url = '/workflow/new';
|
||||||
elements = {
|
getters = {
|
||||||
workflowNameInput: () => cy.getByTestId('workflow-name-input').then($el => cy.wrap($el.find('input'))),
|
workflowNameInput: () => cy.getByTestId('workflow-name-input').then($el => cy.wrap($el.find('input'))),
|
||||||
workflowImportInput: () => cy.getByTestId('workflow-import-input'),
|
workflowImportInput: () => cy.getByTestId('workflow-import-input'),
|
||||||
workflowTags: () => cy.getByTestId('workflow-tags'),
|
workflowTags: () => cy.getByTestId('workflow-tags'),
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BasePage } from "./base";
|
||||||
|
|
||||||
export class WorkflowsPage extends BasePage {
|
export class WorkflowsPage extends BasePage {
|
||||||
url = '/workflows';
|
url = '/workflows';
|
||||||
elements = {
|
getters = {
|
||||||
newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'),
|
newWorkflowButtonCard: () => cy.getByTestId('new-workflow-card'),
|
||||||
newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'),
|
newWorkflowTemplateCard: () => cy.getByTestId('new-workflow-template-card'),
|
||||||
searchBar: () => cy.getByTestId('resources-list-search'),
|
searchBar: () => cy.getByTestId('resources-list-search'),
|
||||||
|
@ -11,13 +11,13 @@ export class WorkflowsPage extends BasePage {
|
||||||
workflowCard: (workflowName: string) => cy.getByTestId(`workflow-card`)
|
workflowCard: (workflowName: string) => cy.getByTestId(`workflow-card`)
|
||||||
.contains(workflowName)
|
.contains(workflowName)
|
||||||
.parents('[data-test-id="workflow-card"]'),
|
.parents('[data-test-id="workflow-card"]'),
|
||||||
workflowTags: (workflowName: string) => this.elements.workflowCard(workflowName)
|
workflowTags: (workflowName: string) => this.getters.workflowCard(workflowName)
|
||||||
.findChildByTestId('workflow-card-tags'),
|
.findChildByTestId('workflow-card-tags'),
|
||||||
workflowActivator: (workflowName: string) => this.elements.workflowCard(workflowName)
|
workflowActivator: (workflowName: string) => this.getters.workflowCard(workflowName)
|
||||||
.findChildByTestId('workflow-card-activator'),
|
.findChildByTestId('workflow-card-activator'),
|
||||||
workflowActivatorStatus: (workflowName: string) => this.elements.workflowActivator(workflowName)
|
workflowActivatorStatus: (workflowName: string) => this.getters.workflowActivator(workflowName)
|
||||||
.findChildByTestId('workflow-activator-status'),
|
.findChildByTestId('workflow-activator-status'),
|
||||||
workflowCardActions: (workflowName: string) => this.elements.workflowCard(workflowName)
|
workflowCardActions: (workflowName: string) => this.getters.workflowCard(workflowName)
|
||||||
.findChildByTestId('workflow-card-actions'),
|
.findChildByTestId('workflow-card-actions'),
|
||||||
workflowDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete')
|
workflowDeleteButton: () => cy.getByTestId('action-toggle-dropdown').filter(':visible').contains('Delete')
|
||||||
// Not yet implemented
|
// Not yet implemented
|
||||||
|
|
|
@ -36,13 +36,13 @@ Cypress.Commands.add('createFixtureWorkflow', (fixtureKey, workflowName) => {
|
||||||
const WorkflowPage = new WorkflowPageClass()
|
const WorkflowPage = new WorkflowPageClass()
|
||||||
|
|
||||||
// We need to force the click because the input is hidden
|
// We need to force the click because the input is hidden
|
||||||
WorkflowPage.get('workflowImportInput').selectFile(`cypress/fixtures/${fixtureKey}`, { force: true});
|
WorkflowPage.getters.workflowImportInput().selectFile(`cypress/fixtures/${fixtureKey}`, { force: true});
|
||||||
WorkflowPage.get('workflowNameInput').should('be.disabled');
|
WorkflowPage.getters.workflowNameInput().should('be.disabled');
|
||||||
WorkflowPage.get('workflowNameInput').parent().click()
|
WorkflowPage.getters.workflowNameInput().parent().click()
|
||||||
WorkflowPage.get('workflowNameInput').should('be.enabled');
|
WorkflowPage.getters.workflowNameInput().should('be.enabled');
|
||||||
WorkflowPage.get('workflowNameInput').clear().type(workflowName).type('{enter}');
|
WorkflowPage.getters.workflowNameInput().clear().type(workflowName).type('{enter}');
|
||||||
|
|
||||||
WorkflowPage.get('saveButton').should('contain', 'Saved');
|
WorkflowPage.getters.saveButton().should('contain', 'Saved');
|
||||||
})
|
})
|
||||||
|
|
||||||
Cypress.Commands.add('findChildByTestId', { prevSubject: true }, (subject: Cypress.Chainable<JQuery<HTMLElement>>, childTestId) => {
|
Cypress.Commands.add('findChildByTestId', { prevSubject: true }, (subject: Cypress.Chainable<JQuery<HTMLElement>>, childTestId) => {
|
||||||
|
@ -58,10 +58,10 @@ Cypress.Commands.add(
|
||||||
cy.session([email, password], () => {
|
cy.session([email, password], () => {
|
||||||
cy.visit(signinPage.url);
|
cy.visit(signinPage.url);
|
||||||
|
|
||||||
signinPage.get('form').within(() => {
|
signinPage.getters.form().within(() => {
|
||||||
signinPage.get('email').type(email);
|
signinPage.getters.email().type(email);
|
||||||
signinPage.get('password').type(password);
|
signinPage.getters.password().type(password);
|
||||||
signinPage.get('submit').click();
|
signinPage.getters.submit().click();
|
||||||
});
|
});
|
||||||
|
|
||||||
// we should be redirected to /workflows
|
// we should be redirected to /workflows
|
||||||
|
@ -79,14 +79,14 @@ Cypress.Commands.add('signup', (email, firstName, lastName, password) => {
|
||||||
|
|
||||||
cy.visit(signupPage.url);
|
cy.visit(signupPage.url);
|
||||||
|
|
||||||
signupPage.get('form').within(() => {
|
signupPage.getters.form().within(() => {
|
||||||
cy.url().then((url) => {
|
cy.url().then((url) => {
|
||||||
if (url.endsWith(signupPage.url)) {
|
if (url.endsWith(signupPage.url)) {
|
||||||
signupPage.get('email').type(email);
|
signupPage.getters.email().type(email);
|
||||||
signupPage.get('firstName').type(firstName);
|
signupPage.getters.firstName().type(firstName);
|
||||||
signupPage.get('lastName').type(lastName);
|
signupPage.getters.lastName().type(lastName);
|
||||||
signupPage.get('password').type(password);
|
signupPage.getters.password().type(password);
|
||||||
signupPage.get('submit').click();
|
signupPage.getters.submit().click();
|
||||||
} else {
|
} else {
|
||||||
cy.log('User already signed up');
|
cy.log('User already signed up');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
export type IE2ETestPageElement = (...args: any[]) =>
|
export type IE2ETestPageElement = (...args: any[]) =>
|
||||||
| Cypress.Chainable<JQuery<HTMLElement>>
|
| Cypress.Chainable<JQuery<HTMLElement>>
|
||||||
|
| Cypress.Chainable<JQuery<HTMLInputElement>>
|
||||||
| Cypress.Chainable<JQuery<HTMLButtonElement>>;
|
| Cypress.Chainable<JQuery<HTMLButtonElement>>;
|
||||||
|
|
||||||
export interface IE2ETestPage {
|
export interface IE2ETestPage {
|
||||||
url?: string;
|
url?: string;
|
||||||
elements: Record<string, IE2ETestPageElement>;
|
getters: Record<string, IE2ETestPageElement>;
|
||||||
get(id: string, ...args: unknown[]): ReturnType<IE2ETestPageElement>;
|
actions: Record<string, (...args: any[]) => void>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,11 @@
|
||||||
"webhook": "./packages/cli/bin/n8n webhook",
|
"webhook": "./packages/cli/bin/n8n webhook",
|
||||||
"worker": "./packages/cli/bin/n8n worker",
|
"worker": "./packages/cli/bin/n8n worker",
|
||||||
"cypress:install": "cypress install",
|
"cypress:install": "cypress install",
|
||||||
"test:e2e:db:clean": "rimraf ~/.n8n/cypress.sqlite ~/.n8n/cypress.sqlite.bak",
|
"test:e2e:db:clean": "rimraf ~/.n8n/cypress.sqlite",
|
||||||
"test:e2e:cypress:run": "cypress run",
|
"test:e2e:cypress:run": "cypress run",
|
||||||
"test:e2e": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false start-server-and-test start http://localhost:5678/favicon.ico test:e2e:cypress:run",
|
"test:e2e": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false start-server-and-test start http://localhost:5678/favicon.ico test:e2e:cypress:run",
|
||||||
"test:e2e:cypress:dev": "cypress open",
|
"test:e2e:cypress:dev": "cypress open",
|
||||||
"test:e2e:dev": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false start-server-and-test start http://localhost:5678/favicon.ico test:e2e:cypress:dev",
|
"test:e2e:dev": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false CYPRESS_BASE_URL=http://localhost:8080 start-server-and-test dev http://localhost:8080/favicon.ico test:e2e:cypress:dev",
|
||||||
"test:e2e:cypress:ci:smoke": "cypress run --headless --spec \"cypress/e2e/0-smoke.cy.ts\"",
|
"test:e2e:cypress:ci:smoke": "cypress run --headless --spec \"cypress/e2e/0-smoke.cy.ts\"",
|
||||||
"test:e2e:ci:smoke": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false start-server-and-test start http://localhost:5678/favicon.ico test:e2e:cypress:ci:smoke"
|
"test:e2e:ci:smoke": "pnpm test:e2e:db:clean && cross-env DB_SQLITE_DATABASE=cypress.sqlite N8N_DIAGNOSTICS_ENABLED=false start-server-and-test start http://localhost:5678/favicon.ico test:e2e:cypress:ci:smoke"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<n8n-card
|
<n8n-card
|
||||||
:class="$style['card-link']"
|
:class="$style['card-link']"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
|
data-test-id="credential-card"
|
||||||
>
|
>
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<credential-icon :credential-type-name="credentialType ? credentialType.name : ''" />
|
<credential-icon :credential-type-name="credentialType ? credentialType.name : ''" />
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
:readonly="!credentialPermissions.updateName"
|
:readonly="!credentialPermissions.updateName"
|
||||||
type="Credential"
|
type="Credential"
|
||||||
@input="onNameEdit"
|
@input="onNameEdit"
|
||||||
|
data-test-id="credential-name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.credActions">
|
<div :class="$style.credActions">
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
:disabled="isSaving"
|
:disabled="isSaving"
|
||||||
:loading="isDeleting"
|
:loading="isDeleting"
|
||||||
@click="deleteCredential"
|
@click="deleteCredential"
|
||||||
|
data-test-id="credential-delete-button"
|
||||||
/>
|
/>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
v-if="(hasUnsavedChanges || credentialId) && credentialPermissions.save"
|
v-if="(hasUnsavedChanges || credentialId) && credentialPermissions.save"
|
||||||
|
@ -41,6 +43,7 @@
|
||||||
? $locale.baseText('credentialEdit.credentialEdit.testing')
|
? $locale.baseText('credentialEdit.credentialEdit.testing')
|
||||||
: $locale.baseText('credentialEdit.credentialEdit.saving')"
|
: $locale.baseText('credentialEdit.credentialEdit.saving')"
|
||||||
@click="saveCredential"
|
@click="saveCredential"
|
||||||
|
data-test-id="credential-save-button"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div @keydown.stop :class="$style.container" v-if="credentialProperties.length">
|
<div @keydown.stop :class="$style.container" v-if="credentialProperties.length">
|
||||||
<form v-for="parameter in credentialProperties" :key="parameter.name" autocomplete="off">
|
<form v-for="parameter in credentialProperties" :key="parameter.name" autocomplete="off" data-test-id="credential-connection-parameter">
|
||||||
<!-- Why form? to break up inputs, to prevent Chrome autofill -->
|
<!-- Why form? to break up inputs, to prevent Chrome autofill -->
|
||||||
<n8n-notice
|
<n8n-notice
|
||||||
v-if="parameter.type === 'notice'"
|
v-if="parameter.type === 'notice'"
|
||||||
|
|
|
@ -13,12 +13,14 @@
|
||||||
@keydown.stop
|
@keydown.stop
|
||||||
@focus="$emit('setFocus')"
|
@focus="$emit('setFocus')"
|
||||||
@blur="$emit('onBlur')"
|
@blur="$emit('onBlur')"
|
||||||
|
data-test-id="credential-select"
|
||||||
>
|
>
|
||||||
<n8n-option
|
<n8n-option
|
||||||
v-for="credType in supportedCredentialTypes"
|
v-for="credType in supportedCredentialTypes"
|
||||||
:value="credType.name"
|
:value="credType.name"
|
||||||
:key="credType.name"
|
:key="credType.name"
|
||||||
:label="credType.displayName"
|
:label="credType.displayName"
|
||||||
|
data-test-id="credential-select-option"
|
||||||
>
|
>
|
||||||
<div class="list-option">
|
<div class="list-option">
|
||||||
<div class="option-headline">
|
<div class="option-headline">
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
ref="select"
|
ref="select"
|
||||||
:value="selected"
|
:value="selected"
|
||||||
@change="onSelect"
|
@change="onSelect"
|
||||||
|
data-test-id="new-credential-type-select"
|
||||||
>
|
>
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<font-awesome-icon icon="search" />
|
<font-awesome-icon icon="search" />
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
:key="credential.name"
|
:key="credential.name"
|
||||||
:label="credential.displayName"
|
:label="credential.displayName"
|
||||||
filterable
|
filterable
|
||||||
|
data-test-id="new-credential-type-select-option"
|
||||||
/>
|
/>
|
||||||
</n8n-select>
|
</n8n-select>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,6 +46,7 @@
|
||||||
size="large"
|
size="large"
|
||||||
:disabled="!selected"
|
:disabled="!selected"
|
||||||
@click="openCredentialType"
|
@click="openCredentialType"
|
||||||
|
data-test-id="new-credential-type-button"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
:close-on-press-escape="closeOnPressEscape"
|
:close-on-press-escape="closeOnPressEscape"
|
||||||
:style="styles"
|
:style="styles"
|
||||||
append-to-body
|
append-to-body
|
||||||
|
:data-test-id="`${this.$props.name}-modal`"
|
||||||
>
|
>
|
||||||
<template #title v-if="$scopedSlots.header">
|
<template #title v-if="$scopedSlots.header">
|
||||||
<slot name="header" v-if="!loading" />
|
<slot name="header" v-if="!loading" />
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
<div class="ph-no-capture" v-if="resources.length === 0">
|
<div class="ph-no-capture" v-if="resources.length === 0">
|
||||||
<slot name="empty">
|
<slot name="empty">
|
||||||
<n8n-action-box
|
<n8n-action-box
|
||||||
|
data-test-id="empty-resources-list"
|
||||||
emoji="👋"
|
emoji="👋"
|
||||||
:heading="$locale.baseText(usersStore.currentUser.firstName ? `${resourceKey}.empty.heading` : `${resourceKey}.empty.heading.userNotSetup`, {
|
:heading="$locale.baseText(usersStore.currentUser.firstName ? `${resourceKey}.empty.heading` : `${resourceKey}.empty.heading.userNotSetup`, {
|
||||||
interpolate: { name: usersStore.currentUser.firstName }
|
interpolate: { name: usersStore.currentUser.firstName }
|
||||||
|
|
Loading…
Reference in a new issue