From 703ef51685abbb375c7983adcfe03d290f8cc795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Tue, 26 May 2020 23:45:29 +0200 Subject: [PATCH] Client portal tests (using Cypress.io) (#3758) * Install Cypress * Fix npm vulnerabilities * scaffold cypress * ignore local tests * login page test * invoices tests * recurring invoices * payments tests * payment methods tests * update invoices & quotes * credits tests --- .prettierrc | 5 + cypress.json | 4 + cypress/fixtures/example.json | 5 + .../integration/client_portal/credits.spec.js | 37 + .../client_portal/invoices.spec.js | 73 + .../integration/client_portal/login.spec.js | 48 + .../client_portal/payment_methods.spec.js | 39 + .../client_portal/payments.spec.js | 46 + .../integration/client_portal/quotes.spec.js | 73 + .../client_portal/recurring_invoices.spec.js | 48 + cypress/integration/examples/actions.spec.js | 298 +++ cypress/integration/examples/aliasing.spec.js | 42 + .../integration/examples/assertions.spec.js | 168 ++ .../integration/examples/connectors.spec.js | 97 + cypress/integration/examples/cookies.spec.js | 78 + .../integration/examples/cypress_api.spec.js | 222 +++ cypress/integration/examples/files.spec.js | 114 ++ .../examples/local_storage.spec.js | 52 + cypress/integration/examples/location.spec.js | 32 + cypress/integration/examples/misc.spec.js | 92 + .../integration/examples/navigation.spec.js | 56 + .../examples/network_requests.spec.js | 195 ++ cypress/integration/examples/querying.spec.js | 114 ++ .../examples/spies_stubs_clocks.spec.js | 95 + .../integration/examples/traversal.spec.js | 121 ++ .../integration/examples/utilities.spec.js | 133 ++ cypress/integration/examples/viewport.spec.js | 59 + cypress/integration/examples/waiting.spec.js | 34 + cypress/integration/examples/window.spec.js | 22 + cypress/plugins/index.js | 24 + cypress/support/commands.js | 46 + cypress/support/index.js | 20 + package-lock.json | 1729 ++++++++++++++--- package.json | 4 +- .../portal/ninja2020/auth/login.blade.php | 7 +- .../livewire/credits-table.blade.php | 2 +- .../livewire/invoices-table.blade.php | 14 +- .../livewire/payment-methods-table.blade.php | 2 +- .../livewire/payments-table.blade.php | 2 +- .../livewire/quotes-table.blade.php | 18 +- .../recurring-invoices-table.blade.php | 6 +- .../ninja2020/components/test.blade.php | 3 + .../portal/ninja2020/invoices/index.blade.php | 4 +- 43 files changed, 4011 insertions(+), 272 deletions(-) create mode 100644 .prettierrc create mode 100644 cypress.json create mode 100644 cypress/fixtures/example.json create mode 100644 cypress/integration/client_portal/credits.spec.js create mode 100644 cypress/integration/client_portal/invoices.spec.js create mode 100644 cypress/integration/client_portal/login.spec.js create mode 100644 cypress/integration/client_portal/payment_methods.spec.js create mode 100644 cypress/integration/client_portal/payments.spec.js create mode 100644 cypress/integration/client_portal/quotes.spec.js create mode 100644 cypress/integration/client_portal/recurring_invoices.spec.js create mode 100644 cypress/integration/examples/actions.spec.js create mode 100644 cypress/integration/examples/aliasing.spec.js create mode 100644 cypress/integration/examples/assertions.spec.js create mode 100644 cypress/integration/examples/connectors.spec.js create mode 100644 cypress/integration/examples/cookies.spec.js create mode 100644 cypress/integration/examples/cypress_api.spec.js create mode 100644 cypress/integration/examples/files.spec.js create mode 100644 cypress/integration/examples/local_storage.spec.js create mode 100644 cypress/integration/examples/location.spec.js create mode 100644 cypress/integration/examples/misc.spec.js create mode 100644 cypress/integration/examples/navigation.spec.js create mode 100644 cypress/integration/examples/network_requests.spec.js create mode 100644 cypress/integration/examples/querying.spec.js create mode 100644 cypress/integration/examples/spies_stubs_clocks.spec.js create mode 100644 cypress/integration/examples/traversal.spec.js create mode 100644 cypress/integration/examples/utilities.spec.js create mode 100644 cypress/integration/examples/viewport.spec.js create mode 100644 cypress/integration/examples/waiting.spec.js create mode 100644 cypress/integration/examples/window.spec.js create mode 100644 cypress/plugins/index.js create mode 100644 cypress/support/commands.js create mode 100644 cypress/support/index.js create mode 100644 resources/views/portal/ninja2020/components/test.blade.php diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000000..d0cb5a51be99 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/cypress.json b/cypress.json new file mode 100644 index 000000000000..dec602eece9a --- /dev/null +++ b/cypress.json @@ -0,0 +1,4 @@ +{ + "video": false, + "baseUrl": "http://127.0.0.1:8002/" +} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 000000000000..da18d9352a17 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/integration/client_portal/credits.spec.js b/cypress/integration/client_portal/credits.spec.js new file mode 100644 index 000000000000..e2c2cc0ec5e5 --- /dev/null +++ b/cypress/integration/client_portal/credits.spec.js @@ -0,0 +1,37 @@ +describe('Credits', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + it('should show credits page', () => { + cy.visit('/client/credits'); + cy.location().should(location => { + expect(location.pathname).to.eq('/client/credits'); + }); + }); + + it('should show credits text', () => { + cy.visit('/client/credits'); + + cy.get('body') + .find('h3') + .first() + .should('contain.text', 'Credits'); + }); + + it('should have required table elements', () => { + cy.visit('/client/credits'); + + cy.get('body') + .find('table.credits-table > tbody > tr') + .first() + .find('a') + .first() + .should('contain.text', 'View') + .click() + .location() + .should(location => { + expect(location.pathname).to.eq('/client/credits/VolejRejNm'); + }); + }); +}); diff --git a/cypress/integration/client_portal/invoices.spec.js b/cypress/integration/client_portal/invoices.spec.js new file mode 100644 index 000000000000..cb7e309c5231 --- /dev/null +++ b/cypress/integration/client_portal/invoices.spec.js @@ -0,0 +1,73 @@ +context('Invoices', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + it('should show invoices page', () => { + cy.visit('/client/invoices'); + cy.location().should(location => { + expect(location.pathname).to.eq('/client/invoices'); + }); + }); + + it('should show invoices text', () => { + cy.visit('/client/invoices'); + + cy.get('body') + .find('h3') + .first() + .should('contain.text', 'Invoices'); + }); + + it('should show download and pay now buttons', () => { + cy.visit('/client/invoices'); + + cy.get('body') + .find('button[value="download"]') + .first() + .should('contain.text', 'Download'); + + cy.get('body') + .find('button[value="payment"]') + .first() + .should('contain.text', 'Pay Now'); + }); + + it('should have per page options dropdown', () => { + cy.visit('/client/invoices'); + + cy.get('body') + .find('select') + .first() + .should('have.value', '10'); + }); + + it('should have required table elements', () => { + cy.visit('/client/invoices'); + + cy.get('body') + .find('table.invoices-table > tbody > tr') + .first() + .find('.button-link') + .first() + .should('contain.text', 'View') + .click() + .location() + .should(location => { + expect(location.pathname).to.eq('/client/invoices/VolejRejNm'); + }); + }); + + it('should filter table content', () => { + cy.visit('/client/invoices'); + + cy.get('body') + .find('#paid-checkbox') + .check(); + + cy.get('body') + .find('table.invoices-table > tbody > tr') + .first() + .should('not.contain', 'Overdue'); + }); +}); diff --git a/cypress/integration/client_portal/login.spec.js b/cypress/integration/client_portal/login.spec.js new file mode 100644 index 000000000000..81ce5ebff781 --- /dev/null +++ b/cypress/integration/client_portal/login.spec.js @@ -0,0 +1,48 @@ +context('Login', () => { + beforeEach(() => { + cy.visit('/client/login'); + }); + + it('should type into login form elements', () => { + cy.get('#test_email') + .invoke('val') + .then(emailValue => { + cy.get('#email') + .type(emailValue) + .should('have.value', emailValue); + }); + + cy.get('#test_password') + .invoke('val') + .then(passwordValue => { + cy.get('#password') + .type(passwordValue) + .should('have.value', passwordValue); + }); + }); + + it('should login into client portal', () => { + cy.get('#test_email') + .invoke('val') + .then(emailValue => { + cy.get('#test_password') + .invoke('val') + .then(passwordValue => { + cy.get('#email') + .type(emailValue) + .should('have.value', emailValue); + cy.get('#password') + .type(passwordValue) + .should('have.value', passwordValue); + cy.get('#loginBtn') + .contains('Login') + .click(); + cy.location().should(location => { + expect(location.pathname).to.eq( + '/client/dashboard' + ); + }); + }); + }); + }); +}); diff --git a/cypress/integration/client_portal/payment_methods.spec.js b/cypress/integration/client_portal/payment_methods.spec.js new file mode 100644 index 000000000000..4b50c96e5e95 --- /dev/null +++ b/cypress/integration/client_portal/payment_methods.spec.js @@ -0,0 +1,39 @@ +context('Payment methods', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + it('should show payment methods page', () => { + cy.visit('/client/payment_methods'); + cy.location().should(location => { + expect(location.pathname).to.eq('/client/payment_methods'); + }); + }); + + it('should show payment methods text', () => { + cy.visit('/client/payment_methods'); + + cy.get('body') + .find('h3') + .first() + .should('contain.text', 'Payment Method'); + }); + + it('should show add payment method button', () => { + cy.visit('/client/payment_methods'); + + cy.get('body') + .find('a.button.button-primary') + .first() + .should('contain.text', 'Add Payment Method'); + }); + + it('should have per page options dropdown', () => { + cy.visit('/client/payment_methods'); + + cy.get('body') + .find('select') + .first() + .should('have.value', '10'); + }); +}); diff --git a/cypress/integration/client_portal/payments.spec.js b/cypress/integration/client_portal/payments.spec.js new file mode 100644 index 000000000000..9db3c931c3c3 --- /dev/null +++ b/cypress/integration/client_portal/payments.spec.js @@ -0,0 +1,46 @@ +context('Payments', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + it('should show payments page', () => { + cy.visit('/client/payments'); + cy.location().should(location => { + expect(location.pathname).to.eq('/client/payments'); + }); + }); + + it('should show payments text', () => { + cy.visit('/client/payments'); + + cy.get('body') + .find('h3') + .first() + .should('contain.text', 'Payments'); + }); + + it('should have per page options dropdown', () => { + cy.visit('/client/payments'); + + cy.get('body') + .find('select') + .first() + .should('have.value', '10'); + }); + + it('should have required table elements', () => { + cy.visit('/client/payments'); + + cy.get('body') + .find('table.payments-table > tbody > tr') + .first() + .find('a') + .first() + .should('contain.text', 'View') + .click() + .location() + .should(location => { + expect(location.pathname).to.eq('/client/payments/VolejRejNm'); + }); + }); +}) \ No newline at end of file diff --git a/cypress/integration/client_portal/quotes.spec.js b/cypress/integration/client_portal/quotes.spec.js new file mode 100644 index 000000000000..8627065fccf6 --- /dev/null +++ b/cypress/integration/client_portal/quotes.spec.js @@ -0,0 +1,73 @@ +describe('Quotes', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + it('should show quotes page', () => { + cy.visit('/client/quotes'); + cy.location().should(location => { + expect(location.pathname).to.eq('/client/quotes'); + }); + }); + + it('should show quotes text', () => { + cy.visit('/client/quotes'); + + cy.get('body') + .find('h3') + .first() + .should('contain.text', 'Quotes'); + }); + + it('should show download and approve buttons', () => { + cy.visit('/client/quotes'); + + cy.get('body') + .find('button[value="download"]') + .first() + .should('contain.text', 'Download'); + + cy.get('body') + .find('button[value="approve"]') + .first() + .should('contain.text', 'Approve'); + }); + + it('should have per page options dropdown', () => { + cy.visit('/client/quotes'); + + cy.get('body') + .find('select') + .first() + .should('have.value', '10'); + }); + + it('should have required table elements', () => { + cy.visit('/client/quotes'); + + cy.get('body') + .find('table.quotes-table > tbody > tr') + .first() + .find('.button-link') + .first() + .should('contain.text', 'View') + .click() + .location() + .should(location => { + expect(location.pathname).to.eq('/client/quotes/VolejRejNm'); + }); + }); + + it('should filter table content', () => { + cy.visit('/client/quotes'); + + cy.get('body') + .find('#draft-checkbox') + .check(); + + cy.get('body') + .find('table.quotes-table > tbody > tr') + .first() + .should('not.contain', 'Sent'); + }); +}); diff --git a/cypress/integration/client_portal/recurring_invoices.spec.js b/cypress/integration/client_portal/recurring_invoices.spec.js new file mode 100644 index 000000000000..5271c8e24541 --- /dev/null +++ b/cypress/integration/client_portal/recurring_invoices.spec.js @@ -0,0 +1,48 @@ +context('Recurring invoices', () => { + beforeEach(() => { + cy.clientLogin(); + }); + + // test url + + it('should show recurring invoices page', () => { + cy.visit('/client/recurring_invoices'); + + cy.location().should(location => { + expect(location.pathname).to.eq('/client/recurring_invoices'); + }); + }); + + it('should show reucrring invoices text', () => { + cy.visit('/client/recurring_invoices'); + + cy.get('h3') + .first() + .should('contain.text', 'Recurring Invoices'); + }); + + it('should have per page options dropdown', () => { + cy.visit('/client/recurring_invoices'); + + cy.get('body') + .find('select') + .first() + .should('have.value', '10'); + }); + + it('should have required table elements', () => { + cy.visit('/client/recurring_invoices'); + + cy.get('body') + .find('table.recurring-invoices-table > tbody > tr') + .first() + .find('a') + .first() + .should('contain.text', 'View') + .click() + .location() + .should(location => { + expect(location.pathname).to.eq('/client/recurring_invoices/VolejRejNm'); + }); + }); +}); diff --git a/cypress/integration/examples/actions.spec.js b/cypress/integration/examples/actions.spec.js new file mode 100644 index 000000000000..f26ba6343585 --- /dev/null +++ b/cypress/integration/examples/actions.spec.js @@ -0,0 +1,298 @@ +/// + +context('Actions', () => { + beforeEach(() => { + cy.visit('https://example.cypress.io/commands/actions') + }) + + // https://on.cypress.io/interacting-with-elements + + it('.type() - type into a DOM element', () => { + // https://on.cypress.io/type + cy.get('.action-email') + .type('fake@email.com').should('have.value', 'fake@email.com') + + // .type() with special character sequences + .type('{leftarrow}{rightarrow}{uparrow}{downarrow}') + .type('{del}{selectall}{backspace}') + + // .type() with key modifiers + .type('{alt}{option}') //these are equivalent + .type('{ctrl}{control}') //these are equivalent + .type('{meta}{command}{cmd}') //these are equivalent + .type('{shift}') + + // Delay each keypress by 0.1 sec + .type('slow.typing@email.com', { delay: 100 }) + .should('have.value', 'slow.typing@email.com') + + cy.get('.action-disabled') + // Ignore error checking prior to type + // like whether the input is visible or disabled + .type('disabled error checking', { force: true }) + .should('have.value', 'disabled error checking') + }) + + it('.focus() - focus on a DOM element', () => { + // https://on.cypress.io/focus + cy.get('.action-focus').focus() + .should('have.class', 'focus') + .prev().should('have.attr', 'style', 'color: orange;') + }) + + it('.blur() - blur off a DOM element', () => { + // https://on.cypress.io/blur + cy.get('.action-blur').type('About to blur').blur() + .should('have.class', 'error') + .prev().should('have.attr', 'style', 'color: red;') + }) + + it('.clear() - clears an input or textarea element', () => { + // https://on.cypress.io/clear + cy.get('.action-clear').type('Clear this text') + .should('have.value', 'Clear this text') + .clear() + .should('have.value', '') + }) + + it('.submit() - submit a form', () => { + // https://on.cypress.io/submit + cy.get('.action-form') + .find('[type="text"]').type('HALFOFF') + cy.get('.action-form').submit() + .next().should('contain', 'Your form has been submitted!') + }) + + it('.click() - click on a DOM element', () => { + // https://on.cypress.io/click + cy.get('.action-btn').click() + + // You can click on 9 specific positions of an element: + // ----------------------------------- + // | topLeft top topRight | + // | | + // | | + // | | + // | left center right | + // | | + // | | + // | | + // | bottomLeft bottom bottomRight | + // ----------------------------------- + + // clicking in the center of the element is the default + cy.get('#action-canvas').click() + + cy.get('#action-canvas').click('topLeft') + cy.get('#action-canvas').click('top') + cy.get('#action-canvas').click('topRight') + cy.get('#action-canvas').click('left') + cy.get('#action-canvas').click('right') + cy.get('#action-canvas').click('bottomLeft') + cy.get('#action-canvas').click('bottom') + cy.get('#action-canvas').click('bottomRight') + + // .click() accepts an x and y coordinate + // that controls where the click occurs :) + + cy.get('#action-canvas') + .click(80, 75) // click 80px on x coord and 75px on y coord + .click(170, 75) + .click(80, 165) + .click(100, 185) + .click(125, 190) + .click(150, 185) + .click(170, 165) + + // click multiple elements by passing multiple: true + cy.get('.action-labels>.label').click({ multiple: true }) + + // Ignore error checking prior to clicking + cy.get('.action-opacity>.btn').click({ force: true }) + }) + + it('.dblclick() - double click on a DOM element', () => { + // https://on.cypress.io/dblclick + + // Our app has a listener on 'dblclick' event in our 'scripts.js' + // that hides the div and shows an input on double click + cy.get('.action-div').dblclick().should('not.be.visible') + cy.get('.action-input-hidden').should('be.visible') + }) + + it('.rightclick() - right click on a DOM element', () => { + // https://on.cypress.io/rightclick + + // Our app has a listener on 'contextmenu' event in our 'scripts.js' + // that hides the div and shows an input on right click + cy.get('.rightclick-action-div').rightclick().should('not.be.visible') + cy.get('.rightclick-action-input-hidden').should('be.visible') + }) + + it('.check() - check a checkbox or radio element', () => { + // https://on.cypress.io/check + + // By default, .check() will check all + // matching checkbox or radio elements in succession, one after another + cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]') + .check().should('be.checked') + + cy.get('.action-radios [type="radio"]').not('[disabled]') + .check().should('be.checked') + + // .check() accepts a value argument + cy.get('.action-radios [type="radio"]') + .check('radio1').should('be.checked') + + // .check() accepts an array of values + cy.get('.action-multiple-checkboxes [type="checkbox"]') + .check(['checkbox1', 'checkbox2']).should('be.checked') + + // Ignore error checking prior to checking + cy.get('.action-checkboxes [disabled]') + .check({ force: true }).should('be.checked') + + cy.get('.action-radios [type="radio"]') + .check('radio3', { force: true }).should('be.checked') + }) + + it('.uncheck() - uncheck a checkbox element', () => { + // https://on.cypress.io/uncheck + + // By default, .uncheck() will uncheck all matching + // checkbox elements in succession, one after another + cy.get('.action-check [type="checkbox"]') + .not('[disabled]') + .uncheck().should('not.be.checked') + + // .uncheck() accepts a value argument + cy.get('.action-check [type="checkbox"]') + .check('checkbox1') + .uncheck('checkbox1').should('not.be.checked') + + // .uncheck() accepts an array of values + cy.get('.action-check [type="checkbox"]') + .check(['checkbox1', 'checkbox3']) + .uncheck(['checkbox1', 'checkbox3']).should('not.be.checked') + + // Ignore error checking prior to unchecking + cy.get('.action-check [disabled]') + .uncheck({ force: true }).should('not.be.checked') + }) + + it('.select() - select an option in a + +@endcomponent + @section('body')
-
diff --git a/resources/views/portal/ninja2020/components/livewire/credits-table.blade.php b/resources/views/portal/ninja2020/components/livewire/credits-table.blade.php index 30892870fb83..dc9bf5191adb 100644 --- a/resources/views/portal/ninja2020/components/livewire/credits-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/credits-table.blade.php @@ -12,7 +12,7 @@
- +
diff --git a/resources/views/portal/ninja2020/components/livewire/invoices-table.blade.php b/resources/views/portal/ninja2020/components/livewire/invoices-table.blade.php index c518f8974fb2..a70383ec8993 100644 --- a/resources/views/portal/ninja2020/components/livewire/invoices-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/invoices-table.blade.php @@ -11,22 +11,22 @@
- - + +
- - + +
- - + +
- +
diff --git a/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php b/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php index 93d13be14467..104d8a384ea0 100644 --- a/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/payment-methods-table.blade.php @@ -12,7 +12,7 @@
- +
diff --git a/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php b/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php index 7e1c51d99fcd..4813665cbc28 100644 --- a/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/payments-table.blade.php @@ -11,7 +11,7 @@
- +
diff --git a/resources/views/portal/ninja2020/components/livewire/quotes-table.blade.php b/resources/views/portal/ninja2020/components/livewire/quotes-table.blade.php index eff45ee0e94d..eddeec9a1f44 100644 --- a/resources/views/portal/ninja2020/components/livewire/quotes-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/quotes-table.blade.php @@ -11,26 +11,26 @@
- - + +
- - + +
- - + +
- - + +
- +
diff --git a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php index fbe8ab13d9cd..ccc81c0a668c 100644 --- a/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/recurring-invoices-table.blade.php @@ -12,7 +12,7 @@
- +
@@ -87,7 +87,3 @@ {{ $invoices->links() }} - -@push('footer') - -@endpush \ No newline at end of file diff --git a/resources/views/portal/ninja2020/components/test.blade.php b/resources/views/portal/ninja2020/components/test.blade.php new file mode 100644 index 000000000000..9d7a228d41c7 --- /dev/null +++ b/resources/views/portal/ninja2020/components/test.blade.php @@ -0,0 +1,3 @@ +@if(app()->environment() == 'testing' || app()->environment() == 'local') + {{ $slot }} +@endif \ No newline at end of file diff --git a/resources/views/portal/ninja2020/invoices/index.blade.php b/resources/views/portal/ninja2020/invoices/index.blade.php index e78ec1c2257b..d6fe163dca39 100644 --- a/resources/views/portal/ninja2020/invoices/index.blade.php +++ b/resources/views/portal/ninja2020/invoices/index.blade.php @@ -11,7 +11,7 @@ @endif -
+
@@ -19,7 +19,7 @@ {{ ctrans('texts.invoices') }}
-

+

{{ ctrans('texts.list_of_invoices') }}