Merge pull request #4168 from beganovich/v2-cypress-payments-tests

(v5) Stripe testing using Cypress
This commit is contained in:
David Bomba 2020-10-15 20:16:50 +11:00 committed by GitHub
commit bf592b0805
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 177 additions and 108 deletions

View File

@ -176,8 +176,8 @@ class CreateSingleAccount extends Command
$client = $company->clients->random(); $client = $company->clients->random();
$this->info('creating credit for client #'.$client->id); // $this->info('creating credit for client #'.$client->id);
$this->createCredit($client); // $this->createCredit($client); /** Prevents Stripe from running payments. */
$client = $company->clients->random(); $client = $company->clients->random();

View File

@ -1,5 +1,5 @@
{ {
"video": false, "video": false,
"baseUrl": "http://ninja.test:8000/", "baseUrl": "http://localhost:8000/",
"chromeWebSecurity": false "chromeWebSecurity": false
} }

View File

@ -0,0 +1,48 @@
import { second } from '../fixtures/example.json';
describe('Checkout Credit Card Payments', () => {
beforeEach(() => {
// cy.useGateway(second);
cy.clientLogin();
});
it('should be able to complete payment using checkout credit card', () => {
cy.visit('/client/invoices');
cy.get('#unpaid-checkbox').click();
cy.get('[data-cy=pay-now')
.first()
.click();
cy.location('pathname').should('eq', '/client/invoices/payment');
cy.get('[data-cy=payment-methods-dropdown').click();
cy.get('[data-cy=payment-method')
.first()
.click();
cy.wait(8000);
cy.get('.cko-pay-now.show')
.first()
.click();
cy.wait(3000);
cy.getWithinIframe('[data-checkout="card-number"]').type(
'4242424242424242'
);
cy.getWithinIframe('[data-checkout="expiry-month"]').type('12');
cy.getWithinIframe('[data-checkout="expiry-year"]').type('30');
cy.getWithinIframe('[data-checkout="cvv"]').type('100');
cy.getWithinIframe('.form-submit')
.first()
.click();
cy.wait(5000);
cy.url().should('contain', '/client/payments');
});
});

View File

@ -1,5 +1,10 @@
{ {
"name": "Using fixtures to represent data", "name": "Using fixtures to represent data",
"email": "hello@cypress.io", "email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes" "body": "Fixtures are a great way to mock data for responses to routes",
"first": "VolejRejNm",
"second": "Wpmbk5ezJn",
"url": "http://localhost:8000"
} }

View File

@ -19,7 +19,7 @@ describe('Credits', () => {
.should('contain.text', 'Credits'); .should('contain.text', 'Credits');
}); });
it('should have required table elements', () => { /* it('should have required table elements', () => {
cy.visit('/client/credits'); cy.visit('/client/credits');
cy.get('body') cy.get('body')
@ -33,5 +33,5 @@ describe('Credits', () => {
.should(location => { .should(location => {
expect(location.pathname).to.eq('/client/credits/VolejRejNm'); expect(location.pathname).to.eq('/client/credits/VolejRejNm');
}); });
}); });*/
}); });

View File

@ -19,35 +19,6 @@ context('Payment methods', () => {
.should('contain.text', 'Payment Method'); .should('contain.text', 'Payment Method');
}); });
it('should add stripe credit card', () => {
cy.visit('/client/payment_methods');
cy.get('body')
.find('#add-payment-method')
.first()
.should('contain.text', 'Add Payment Method')
.click()
cy.location().should(location => {
expect(location.pathname).to.eq('/client/payment_methods/create');
});
cy.wait(3000);
cy.get('#cardholder-name').type('Invoice Ninja');
cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242');
cy.getWithinIframe('[name="exp-date"]').type('2442');
cy.getWithinIframe('[name="cvc"]').type('242');
cy.getWithinIframe('[name="postal"]').type('12345');
cy.get('#card-button').click();
cy.location().should(location => {
expect(location.pathname).to.eq('/client/payment_methods');
});
});
it('should have per page options dropdown', () => { it('should have per page options dropdown', () => {
cy.visit('/client/payment_methods'); cy.visit('/client/payment_methods');

View File

@ -27,20 +27,4 @@ context('Payments', () => {
.first() .first()
.should('have.value', '10'); .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');
});
});
})

View File

@ -3,8 +3,6 @@ context('Recurring invoices', () => {
cy.clientLogin(); cy.clientLogin();
}); });
// test url
it('should show recurring invoices page', () => { it('should show recurring invoices page', () => {
cy.visit('/client/recurring_invoices'); cy.visit('/client/recurring_invoices');
@ -29,20 +27,4 @@ context('Recurring invoices', () => {
.first() .first()
.should('have.value', '10'); .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');
});
});
}); });

View File

@ -0,0 +1,47 @@
describe('Stripe Credit Card Payments', () => {
beforeEach(() => {
cy.clientLogin();
});
it('should be able to add credit card using Stripe', () => {
cy.visit('/client/payment_methods');
cy.get('[data-cy=add-payment-method]').click();
cy.get('[data-cy=add-credit-card-link]').click();
cy.get('#cardholder-name').type('Invoice Ninja');
cy.getWithinIframe('[name="cardnumber"]').type('4242424242424242');
cy.getWithinIframe('[name="exp-date"]').type('1230');
cy.getWithinIframe('[name="cvc"]').type('100');
cy.getWithinIframe('[name="postal"]').type('12345');
cy.get('#card-button').click();
cy.get('#errors').should('be.empty');
cy.location('pathname').should('eq', '/client/payment_methods');
});
it('should be able to complete payment with added credit card', () => {
cy.visit('/client/invoices');
cy.get('#unpaid-checkbox').click();
cy.get('[data-cy=pay-now')
.first()
.click();
cy.location('pathname').should('eq', '/client/invoices/payment');
cy.get('[data-cy=payment-methods-dropdown').click();
cy.get('[data-cy=payment-method')
.first()
.click();
cy.get('#pay-now-with-token').click();
cy.url().should('contain', '/client/payments');
});
});

View File

@ -23,15 +23,17 @@
// //
// -- This will overwrite an existing command -- // -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
const axios = require('axios');
const fixture = require('../fixtures/example.json');
Cypress.Commands.add('clientLogin', () => { Cypress.Commands.add('clientLogin', () => {
cy.visit('/client/login'); cy.visit('/client/login');
cy.get('#test_email') cy.get('#test_email')
.invoke('val') .invoke('val')
.then(emailValue => { .then((emailValue) => {
cy.get('#test_password') cy.get('#test_password')
.invoke('val') .invoke('val')
.then(passwordValue => { .then((passwordValue) => {
cy.get('#email') cy.get('#email')
.type(emailValue) .type(emailValue)
.should('have.value', emailValue); .should('have.value', emailValue);
@ -45,32 +47,62 @@ Cypress.Commands.add('clientLogin', () => {
}); });
}); });
Cypress.Commands.add( Cypress.Commands.add('iframeLoaded', { prevSubject: 'element' }, ($iframe) => {
'iframeLoaded', const contentWindow = $iframe.prop('contentWindow');
{prevSubject: 'element'}, return new Promise((resolve) => {
($iframe) => { if (contentWindow) {
const contentWindow = $iframe.prop('contentWindow'); resolve(contentWindow);
return new Promise(resolve => { } else {
if ( $iframe.on('load', () => {
contentWindow resolve(contentWindow);
) { });
resolve(contentWindow) }
} else {
$iframe.on('load', () => {
resolve(contentWindow)
})
}
})
}); });
});
Cypress.Commands.add( Cypress.Commands.add(
'getInDocument', 'getInDocument',
{prevSubject: 'Permission denied to access property "document" on cross-origin object'}, {
prevSubject:
'Permission denied to access property "document" on cross-origin object',
},
(document, selector) => Cypress.$(selector, document) (document, selector) => Cypress.$(selector, document)
); );
Cypress.Commands.add( Cypress.Commands.add('getWithinIframe', (targetElement) =>
'getWithinIframe', cy
(targetElement) => cy.get('iframe').iframeLoaded().its('document').getInDocument(targetElement) .get('iframe')
.iframeLoaded()
.its('document')
.getInDocument(targetElement)
); );
Cypress.Commands.add('useGateway', (gateway) => {
let body = {
settings: {
entity: 'App\\Models\\Client',
industry_id: '',
size_id: '',
currency_id: '1',
company_gateway_ids: gateway,
},
};
let options = {
headers: {
'X-Api-Secret': 'superdoopersecrethere',
'X-Api-Token':
'S0x8behDk8HG8PI0i8RXdpf2AVud5b993pE8vata7xmm4RgW6u3NeGC8ibWIUjZv',
'X-Requested-With': 'XMLHttpRequest',
},
};
axios
.put(
`http://localhost:8000/api/v1/clients/${fixture.first}`,
body,
options
)
.then((response) => console.log(response))
.catch((error) => console.log(error.message));
});

View File

@ -99,7 +99,7 @@
@csrf @csrf
<input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}"> <input type="hidden" name="invoices[]" value="{{ $invoice->hashed_id }}">
<input type="hidden" name="action" value="payment"> <input type="hidden" name="action" value="payment">
<button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary"> <button class="px-2 py-1 mr-3 text-xs uppercase button button-primary bg-primary" data-cy="pay-now">
@lang('texts.pay_now') @lang('texts.pay_now')
</button> </button>
</form> </form>

View File

@ -12,16 +12,16 @@
<div class="relative" x-data="{ open: false }" x-on:click.away="open = false"> <div class="relative" x-data="{ open: false }" x-on:click.away="open = false">
<!-- Add payment method button --> <!-- Add payment method button -->
@if($client->getCreditCardGateway() || $client->getBankTransferGateway()) @if($client->getCreditCardGateway() || $client->getBankTransferGateway())
<button x-on:click="open = !open" class="button button-primary bg-primary">{{ ctrans('texts.add_payment_method') }}</button> <button x-on:click="open = !open" class="button button-primary bg-primary" data-cy="add-payment-method">{{ ctrans('texts.add_payment_method') }}</button>
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg"> <div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg">
<div class="py-1 rounded-md bg-white shadow-xs"> <div class="py-1 rounded-md bg-white shadow-xs">
@if($client->getCreditCardGateway()) @if($client->getCreditCardGateway())
<a href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150"> <a data-cy="add-credit-card-link" href="{{ route('client.payment_methods.create', ['method' => App\Models\GatewayType::CREDIT_CARD]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
{{ ctrans('texts.credit_card') }} {{ ctrans('texts.credit_card') }}
</a> </a>
@endif @endif
@if($client->getBankTransferGateway()) @if($client->getBankTransferGateway())
<a href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150"> <a data-cy="add-bank-account-link" href="{{ route('client.payment_methods.create', ['method' => $client->getBankTransferMethodType()]) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition ease-in-out duration-150">
{{ ctrans('texts.bank_account') }} {{ ctrans('texts.bank_account') }}
</a> </a>
@endif @endif
@ -91,7 +91,7 @@
</svg> </svg>
@endif @endif
</td> </td>
<td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium"> <td class="px-6 py-4 whitespace-no-wrap flex items-center justify-end text-sm leading-5 font-medium" data-cy="view-payment-method">
<a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}" <a href="{{ route('client.payment_methods.show', $payment_method->hashed_id) }}"
class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline"> class="text-blue-600 hover:text-indigo-900 focus:outline-none focus:underline">
@lang('texts.view') @lang('texts.view')

View File

@ -91,7 +91,7 @@
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg> </svg>
<span>{{ __('texts.save') }}</span> <span>{{ __('texts.pay_now') }}</span>
</button> </button>
</div> </div>
@else @else

View File

@ -21,7 +21,7 @@
<div class="flex justify-end mb-2"> <div class="flex justify-end mb-2">
<!-- Pay now button --> <!-- Pay now button -->
@if(count($payment_methods) > 0) @if(count($payment_methods) > 0)
<div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left"> <div x-data="{ open: false }" @keydown.window.escape="open = false" @click.away="open = false" class="relative inline-block text-left" data-cy="payment-methods-dropdown">
<div> <div>
<div class="rounded-md shadow-sm"> <div class="rounded-md shadow-sm">
<button @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800"> <button @click="open = !open" type="button" class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition duration-150 ease-in-out bg-white border border-gray-300 rounded-md hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800">
@ -36,7 +36,7 @@
<div class="bg-white rounded-md shadow-xs"> <div class="bg-white rounded-md shadow-xs">
<div class="py-1"> <div class="py-1">
@foreach($payment_methods as $payment_method) @foreach($payment_methods as $payment_method)
<a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"> <a href="#" @click="{ open = false }" data-company-gateway-id="{{ $payment_method['company_gateway_id'] }}" data-gateway-type-id="{{ $payment_method['gateway_type_id'] }}" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" data-cy="payment-method">
{{ $payment_method['label'] }} {{ $payment_method['label'] }}
</a> </a>
@endforeach @endforeach

View File

@ -36,7 +36,7 @@
<form action="{{ route('client.payment_methods.destroy', [$payment_method->hashed_id, 'method' => $payment_method->gateway_type->id]) }}" method="post"> <form action="{{ route('client.payment_methods.destroy', [$payment_method->hashed_id, 'method' => $payment_method->gateway_type->id]) }}" method="post">
@csrf @csrf
@method('DELETE') @method('DELETE')
<button type="submit" class="button button-danger button-block"> <button type="submit" class="button button-danger button-block" data-cy="confirm-payment-removal">
{{ ctrans('texts.remove') }} {{ ctrans('texts.remove') }}
</button> </button>
</form> </form>

View File

@ -98,7 +98,7 @@
</div> </div>
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center"> <div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }"> <div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
<button class="button button-danger" translate @click="open = true"> <button class="button button-danger" @click="open = true" id="open-delete-popup">
{{ ctrans('texts.remove_payment_method') }} {{ ctrans('texts.remove_payment_method') }}
</button> </button>
@include('portal.ninja2020.payment_methods.includes.modals.removal') @include('portal.ninja2020.payment_methods.includes.modals.removal')