How to test Stripe Google/Apple pay button using Cypress? [duplicate] - cypress

I have to call stripe.redirectToCheckout (https://stripe.com/docs/js/checkout/redirect_to_checkout) in js to take a customer to their stripe checkout page.
I want to use cypress to test the checkout process, but it is not able to handle the stripe redirect as the cypress frame is lost when stripe.redirectToCheckout navigates to the page on Stripe's domain.
I also want to test that Stripe redirects us back to the success or error URL.
Is there any way to force cypress to "reattach" to the page once we've navigated to the Stripe checkout
-or-
Is there any way to get the URL for the Stripe checkout page so we can redirect manually or just know that it was at least called with the right parameters.
I know that testing external sites is considered an "anti-pattern" by the people at cypress (https://github.com/cypress-io/cypress/issues/1496). But how can a very standard web process, checkout, be tested (with a very popular and standard payment service, I will add) in that case? I don't buy that this is an "anti-pattern". This is an important step of the end-to-end test, and Stripe specifically gives us a testing sandbox for this kind of thing.

One common way to e2e test application with external dependencies like stripe is to make a simple mock version of it, that is then applied in e2e testing. The mock can also be applied during development, to speed things up.

Would this help?
it(`check stripe redirection`, () => {
cy.get('#payButton').click();
cy.location('host', { timeout: 20 * 1000 }).should('eq', STRIPE_REDIRECT_URL);
// do some payment stuff here
// ...
// after paying return back to local
cy.location({ timeout: 20 * 1000 }).should((location) => {
expect(location.host).to.eq('localhost:8080')
expect(location.pathname).to.eq('/')
})
})
I've used this method to test keyCloak login. This is the actual code that worked.
describe('authentication', () => {
beforeEach(() => cy.kcLogout());
it('should login with correct credentials', () => {
cy.visit('/');
cy.location('host', { timeout: 20 * 1000 }).should('eq', 'keycloak.dev.mysite.com:8443');
// This happens on https://keycloak.dev.mysite.com:8443/auth/dealm/...
cy.fixture('userCredentials').then((user) => {
cy.get('#username').type(user.email);
cy.get('#password').type(user.password);
cy.get('#kc-login').click();
cy.location({ timeout: 20 * 1000 }).should((location) => {
expect(location.host).to.eq('localhost:8080')
expect(location.pathname).to.eq('/')
})
})
});

I'm giving it a try to use stripe elements instead since it does not redirect and gives me much more control over it.

Related

Ensure that fixtures exists when running a test. Control order of tests running

A lot of this is wrapped in commands, but I've left that part out to make the problem more feasible.
Consider these two tests:
# Test1: Test login for user
- Step1: Logs in manually (go to login-URL, fill out credentials and click 'Log in').
- Step2: Save auth-cookies as fixtures.
# Test2: Test something is dashboard for user.
- Step1: Set auth-cookies (generated in Test1)
- Step2: Visits https:://example.org/dashboard and ensures the user can see the dashboard.
If they run as written as listed above, then everything is fine.
But if Test2 runs before Test1, then Test2 will fail, since Test1 hasn't to generated the cookies yet.
So Test1 is kind of a prerequisite for Test2.
But Test1 doesn't need to run every time Test2 runs - only if the auth-cookies aren't generated.
I wish I could define my Test2 to be like this instead:
Test2: Test something is dashboard for user.
- Step1: Run ensureAuthCookiesExists-command
- Step2: If the AuthCookies.json-fixture doesn't exist, then run Test1
- Step3: Sets auth-cookies (generated in Test1)
- Step4: Visits https:://example.org/dashboard and ensures the user can see the dashboard.
Solution attempt 1: Control by order
For a long time I've done this using this answer: How to control order of tests. And then having my tests defines like this:
{
"baseUrl": "http://localhost:5000",
"testFiles": [
"preparations/*.js",
"feature-1/check-header.spec.js",
"feature-2/check-buttons.spec.js",
"feature-3/check-images.spec.js",
"feature-4/check-404-page.spec.js",
//...
]
}
But that is annoying, since it means that I keep having to add to add new features to that list, which get's annoying.
And this only solves the problem if I want to run all the tests. If I want to run preparations.spec.js and thereafter: feature-2/check-buttons.spec.js. Then I can't do that easily.
Solution attempt 2: Naming tests smartly
I also tried simply naming them appropriately, like explain here: naming your tests in Cypress.
But that pollutes the naming of the tests, making it more cluttered. And it faces the same issues as solution attempt 1 (that I can't easily run two specific tests after one another).
Solution attempt 3: Making a command for it
I considered making a command that tests for it. Here is some pseudo-code:
beforeEach(() => {
if( preparationsHasntCompleted() ){
runPreparations();
}
}
This seems smart, but it would add extra runtime to all my tests.
This may not suit your testing aims, but the new cy.session() can assure cookie is set regardless of test processing order.
Use it in support in beforeEach() to run before every test.
The first test that runs (either test1 or test2) will perform the request, subsequent tests will use cached values (not repeating the requests).
// cypress/support/e2e.js -- v10 support file
beforeEach(() => {
cy.session('init', () => {
// request and set cookies
})
})
// cypress/e2e/test1.spec.cy.js
it('first test', () => {
// the beforeEach() for 1st test will fire the request
...
})
// cypress/e2e/test2.spec.cy.js
it('second test', () => {
// the beforeEach() for 2nd test will set same values as before from cache
// and not resend the request
})
Upside:
performing login once per run (ref runtime concerns)
performing tests in any order
using the same token for all tests in session (if that's important)
Downside:
if obtaining auth cookies manually (vi UI), effectively moving the login test to a beforeEach()
Example logging in via request
Rather than obtaining the auth cookie via UI, it may be possible to get it via cy.request().
Example from the docs,
cy.session([username, password], () => {
cy.request({
method: 'POST',
url: '/login',
body: { username, password },
}).then(({ body }) => {
cy.setCookie('authToken', body.token)
})
})
It is generally not recommended to write tests that depend on each other as stated in the best practices. You can never be sure that they run correctly. In your case if the first test fails all the other ones will fail, even if the component/functionality is functioning propperly. Especially if you test more than just the pure login procedure, e.g. design.
As #Fody said before you can ensure being logged in in the beforeEach() hook.
I would do it like this:
Use one describe to test the login via UI.
Use another describe where you put the login (via REST) in the before() and the following command Cypress.Cookies.preserveOnce('<nameOfTheCookie>'); in the beforeEach() to not delete the test for the following it()s

Intercept and stub third party <script> tags with Cypress

my use case: Testing a third party widget on a website.
For that, we load the widget on the third party as a <script src='https://cdn.com/widget.js'/> tag. What I'd like to do is intercept that JS request and replace https://cdn.com/widget.js with https://localhost:3000/dist/widget.js.
This is so that we can run the cypress tests with the local version of the widget (run in CI on a PR), and make sure that the PR doesn't break the e2e tests, before merging it.
You can modify the body of the initial visit using cy.intercept()
cy.intercept(url, (req) => {
req.continue((res) => {
res.body = res.body.replace('https://cdn.com/widget.js', 'https://localhost:3000/dist/widget.js')
})
}).as('page')
cy.visit(url)
cy.wait('#page')
.its('response.body')
.should('contain', "<script src='https://localhost:3000/dist/widget.js'/>")
You can use blockHosts to prevent the script at that domain from loading completely. That won't let you replace it with a fake wdiget though.
In order to replace the response with something else, you could use Intercept instead.
There's an interesting example of using this and stub to replace Google Analytics here - https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__google-analytics

What is the definition of "each test" when Cypress docs say "between each test"?

The Cypress docs Best Practices section, under the heading "Is resetting the state necessary?" says the following:
Remember, Cypress already automatically clears localStorage, cookies, sessions, etc before each test.
Does "each test" mean "each it() block"?
Given the following code, and it's comments, is the second it() block technically cookie-less, and session-less, and passing just because we're lucky that the page hasn't changed?
describe('When logged in', () => {
before(() => {
cy.customLoginAndSetSomeCookies()
cy.visit('/page-for-logged-in-folks-only')
})
it('I can see some stuff!', () => {
// `before` ran and set up cookies for us.
// `visit` set the page for us.
// So this passes just fine.
cy.get('#welcome').should('contain', 'You can see me!')
})
it('Is this a new test? Am I logged out?', () => {
// Am I technically cookie-less here, but since
// I'm still on this page I can still see this?
// (Passes, but only because no page transition?)
cy.get('#welcome').should('contain', 'You can see me!')
})
})
If I'm correct, then while these may pass fine, the problem comes if we try to do some "logged in" stuff in the second it() block. So the better pattern would probably be to combine these into a larger single block, or use beforeEach instead of before... BUT, I'm not confident in that answer, because I'm not sure the definition of "each test".
I found your answer, from their pricing page :)
What counts as a test recording?
We consider each time the it() function is called to be a single test. Only test runs configured to record to the Dashboard Service when running Cypress headlessly count toward your plan limit.

Workbox cache group is not correct

I'm using workbox-webpack-plugin v5 (the latest) with InjectManifest plugin. The following is my service worker source file:
import { CacheableResponsePlugin } from 'workbox-cacheable-response';
import { clientsClaim, setCacheNameDetails, skipWaiting } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import {
cleanupOutdatedCaches,
createHandlerBoundToURL,
precacheAndRoute,
} from 'workbox-precaching';
import { NavigationRoute, registerRoute, setCatchHandler } from 'workbox-routing';
import { CacheFirst, NetworkOnly, StaleWhileRevalidate } from 'workbox-strategies';
setCacheNameDetails({
precache: 'install-time',
prefix: 'app-precache',
runtime: 'run-time',
suffix: 'v1',
});
cleanupOutdatedCaches();
clientsClaim();
skipWaiting();
precacheAndRoute(self.__WB_MANIFEST);
precacheAndRoute([{ url: '/app-shell.html', revision: 'html-cache-1' }], {
cleanUrls: false,
});
const handler = createHandlerBoundToURL('/app-shell.html');
const navigationRoute = new NavigationRoute(handler);
registerRoute(navigationRoute);
registerRoute(
/.*\.css/,
new CacheFirst({
cacheName: 'css-cache-v1',
})
);
registerRoute(
/^https:\/\/fonts\.(?:googleapis|gstatic)\.com/,
new CacheFirst({
cacheName: 'google-fonts-cache-v1',
plugins: [
new CacheableResponsePlugin({
statuses: [0, 200],
}),
new ExpirationPlugin({
maxAgeSeconds: 60 * 60 * 24 * 365,
maxEntries: 30,
}),
],
})
);
registerRoute(
/.*\.js/,
new StaleWhileRevalidate({
cacheName: 'js-cache-v1',
})
);
setCatchHandler(new NetworkOnly());
I have the following questions/problems:
Cache group is not correct. Everything except google fonts is under workbox-precache-v2 or app-precache-install-time-v1 cache group, not individual cache groups such as css-cache-v1, js-cache-v1. However, 1 in 20 times, it shows correct cache group, and I just can't figure out why.
Google font shows from memory cache. Is it correct? It works fine in offline, but what will happen if the user closes the browser/machine and comes back in offline mode?
Is '/app-shell.html' usage correct? It's an express backend app with * as the wild card for all routes, and React Router handles the routing. Functionally, it's working fine offline. I don't have any app-shell.html page.
Thanks for your help.
Cache group is not correct. Everything except google fonts is under workbox-precache-v2 or app-precache-install-time-v1 cache group, not individual cache groups such as css-cache-v1, js-cache-v1. However, 1 in 20 times, it shows correct cache group, and I just can't figure out why.
It depends on what's in your precache manifest (i.e. what self.__WB_MANIFEST gets replaced with during your webpack build).
For example, let's say you have a file generated by webpack called bundle.js, and that file ends up in your precache manifest. Based on your service worker code, that file will end up in a cached called app-precache-install-time-v1—even though it also matches your runtime route with the cache js-cache-v1.
The reason is because your precache route is registered before you runtime route, so your precaching logic will handle that request rather than your runtime caching logic.
Google font shows from memory cache. Is it correct? It works fine in offline, but what will happen if the user closes the browser/machine and comes back in offline mode?
I believe this means the request is not being handled by the service worker, but you can check the workbox logs in the developer console to verify (and see why not).
Alternatively, you could update your code to use a custom handler that just logs whether it's running like so:
registerRoute(
/^https:\/\/fonts\.(?:googleapis|gstatic)\.com/,
({request}) => {
// Log to make sure this function is being called...
console.log(request.url);
return fetch(request);
}
);
Is '/app-shell.html' usage correct? It's an express backend app with * as the wild card for all routes, and React Router handles the routing. Functionally, it's working fine offline. I don't have any app-shell.html page.
Does your express route respond with a file called app-shell.html? If so, then you'd probably want your precache revision to be a hash of the file itself (rather than revision: 'html-cache-1'). What you have right now should work, but the risk is you'll change the contents of app-shell.html, deploy a new version of your app, but your users will still see the old version because you forgot up update revision: 'html-cache-1'. In general it's best to use revisions generated as part of your build step.

WordPress admin-ajax.php 400 error generated by Event Tickets plugin ... database related?

I have a WordPress website for a client that uses a custom theme and a number of plugins in combination to create an events calendar where logged in users can purchase tickets for paid events and register for free RSVP-required events. To accomplish this, I am using the following six plugins in combination:
The Events Calendar
Events Calendar Pro
Event Tickets
Event Tickets Plus
WooCommerce
WooCommerce Stripe Gateway
The website was established in 2015. In the last 4 years, those plugins have seen extensive updates, and as a result, at a certain point the custom site I built around those plugins started experiencing deprecation issues, and attempts to upgrade caused performance failure. After consulting with support, it became necessary to pull a duplicate copy of the site onto a development server so that I could do the work to upgrade the install so all the latest versions of all of these plugins can be running and working properly.
Everything was going well with the upgrade work until I noticed that one of the plugins seems to be generating the following error in the console in Chrome:
POST https://pcapolar.codewordserver.com/wp-admin/admin-ajax.php 400 (Bad Request)
send # jquery.js?ver=1.12.4-wp:4
ajax # jquery.js?ver=1.12.4-wp:4
n.<computed> # jquery.js?ver=1.12.4-wp:4
r.length.e.checkAvailability # frontend-ticket-form.min.js?ver=4.11.1:1
r.length.e.init # frontend-ticket-form.min.js?ver=4.11.1:1
(anonymous) # frontend-ticket-form.min.js?ver=4.11.1:1
(anonymous) # frontend-ticket-form.min.js?ver=4.11.1:1
Loading the same page in Edge generated the following console error:
HTTP400: BAD REQUEST - The request could not be processed by the server due to invalid syntax
(XHR)POST - https://pcapolar.codewordserver.com/wp-admin/admin-ajax.php
The error occurs only under very specific circumstances -- when a single event page displays the frontend form for a paid ticket. The error doesn't occur when the front end paid ticket form is not output, for example when there is a registered RSVP form with no payment component, when there is no ticket component to the event, or when a user who is not an active member views an affected page such that all information about details of the event and buying tickets is excluded from the output via theme template files. Despite the console error generated, it appears that ajax works fine and all of the functionality works exactly as expected. If you weren't looking at the console, you'd never know there was a problem.
In order to rule out an issue with my custom theme or a conflict with another plugin, I activated the default Twenty Twenty theme and deactivated all other plugins except the 6 I listed that are required to use ticketed event process. The error remained present under these circumstances
After going back and forth with Modern Tribe (the plugin developers) support desk, they report the error cannot be replicated. So I tried myself to install a clean copy of WordPress with a new database, then install only those 6 plugins while running the default Twenty Twenty theme. I did this on the same server under the same cPanel account as the dev site with the error, just at different subdomains. On the clean install, the error was NOT present. But when I then pointed the clean WordPress install to a duplicate copy of the database I'm using for the dev site I'm working on, the error shows up again. From that, I can only conclude there is something going on in my database that is making this error happen, but Modern Tribe support are telling me that since the error can't be reproduced by them, there isn't anything they can do to help.
Starting over with a clean install for this site isn't really an option, there is so much data collected over the last 4 years from ticket purchase and membership transactions that we really can't lose. I need to find the faulty data and clean it, but I feel out of my depth here. Any help or suggestions on how to resolve this are welcome.
Edited to add:
I found this code in the javascript file references by the error message in the console. Am I looking in the right place?
/**
* Check tickets availability.
*
* #since 4.9
*
* #return void
*/
obj.checkAvailability = function() {
// We're checking availability for all the tickets at once.
var params = {
action : 'ticket_availability_check',
tickets : obj.getTickets(),
};
$.post(
TribeTicketOptions.ajaxurl,
params,
function( response ) {
var success = response.success;
// Bail if we don't get a successful response.
if ( ! success ) {
return;
}
// Get the tickets response with availability.
var tickets = response.data.tickets;
// Make DOM updates.
obj.updateAvailability( tickets );
}
);
// Repeat every 60 (filterable via tribe_tickets_availability_check_interval ) seconds
if ( 0 < TribeTicketOptions.availability_check_interval ) {
setTimeout( obj.checkAvailability, TribeTicketOptions.availability_check_interval );
}
}
Edited to add:
Then I ran a string search for ticket_availability and found the following. It looks like it might be related, but I'm a bit over my head in interpreting. Am I on the right track yet?
public function ticket_availability( $tickets = array() ) {
$response = array( 'html' => '' );
$tickets = tribe_get_request_var( 'tickets', array() );
// Bail if we receive no tickets
if ( empty( $tickets ) ) {
wp_send_json_error( $response );
}
/** #var Tribe__Tickets__Tickets_Handler $tickets_handler */
$tickets_handler = tribe( 'tickets.handler' );
/** #var Tribe__Tickets__Editor__Template $tickets_editor */
$tickets_editor = tribe( 'tickets.editor.template' );
// Parse the tickets and create the array for the response
foreach ( $tickets as $ticket_id ) {
$ticket = Tribe__Tickets__Tickets::load_ticket_object( $ticket_id );
if (
! $ticket instanceof Tribe__Tickets__Ticket_Object
|| empty( $ticket->ID )
) {
continue;
}
$available = $tickets_handler->get_ticket_max_purchase( $ticket->ID );
$response['tickets'][ $ticket_id ]['available'] = $available;
// If there are no more available we will send the template part HTML to update the DOM
if ( 0 === $available ) {
$response['tickets'][ $ticket_id ]['unavailable_html'] = $tickets_editor->template( 'blocks/tickets/quantity-unavailable', $ticket, false );
}
}
wp_send_json_success( $response );
}
The weird thing is that this function is filed to a folder called Blocks, which implied it works with Gutenberg, which I have disabled via the Classic Editor plugin.
StackOverflow powers that be, forgive me but this was too much to fit in a comment.
I apologize, but this is more of a debug process than a strict answer.
The admin-ajax.php file only has 3 scenarios to return a 400 error.
If the user is logged in, and the ajax function hasn't been added to the wp_ajax_{function_name} action. (line #164)
The user is NOT logged in, and the ajax function hasn't been added to the wp_ajax_nopriv_{function_name} action. (line #179)
No action was sent in the request. (line #32)
You'll need to figure out which of these is causing your error. If you're not sure how to do this, an easy way is to temporarily edit your admin-ajax.php file. Before you see this:
// Require an action parameter
if ( empty( $_REQUEST['action'] ) ) {
wp_die( '0', 400 );
}
Add in the following (again, before the above lines)
ob_start();
print( '<pre>'. print_r($_REQUEST, true) .'</pre>' );
wp_mail( 'your-email#address.com', 'Debug Results', ob_get_clean() );
This will email you (semi-nicely) formatted dump of the $_REQUEST. If there's no action, you'll know that for some reason the "front end form for a paid ticket" function isn't being added, and Modern Tribe could probably help you out with that.
If the action is set, you can add a similar line down below at line 164 or 179 and repeat the above, but with print( '<pre>'. print_r($action, true) .'</pre>' ); instead. If either of these get emailed to you when you submit the form, you'll know which ajax hook isn't being added, and again Modern Tribe could probably help you from there.
Also note, that modifying core WP files is generally bad practice and you should revert these changes when you're done debugging. (There's ways to hook into file instead, but for ease/speed of diagnostics, go ahead and temporarily edit it, as these aren't permanent changes, and it's on a development site, so you shouldn't have to worry)
Beyond the above, you'll probably need to hire a developer look at it, there's not much more that someone on Stack Overflow can do without having direct access to your database and files.
I am having the same problem and I think the problem is Tribe__Editor::should_load_blocks when the classic editor plugin is active.
To bypass this error I add this code to my theme functions.php file
add_action( 'xxx', tribe_callback( 'tickets.editor.blocks.tickets', 'register' ) );
do_action( 'xxx' );
I hope this works for you.
The advice I received from the plugin developers after a great deal of back and forth was to add the following to my theme's functions.php file:
add_filter( 'tribe_tickets_availability_check_interval', function( $interval) {
return 0;
} );
This resolves the console issue, does not generate additional errors, and does not interfere with any expected functionality in all tests performed thus far.
Wanted to update everyone that with the release of Event Tickets 4.11.4 and Event Tickets Plus 4.11.3, this issue has been completely resolved on the plugin side. So apparently this was not just an issue with my site only. Thank you to everyone who contributed.

Resources