How to stop puppeteer follow redirects - chromium

Currently it seems the default behaviour of puppeteer is to follow redirects and return the DOM at the end of the chain.
How can I make the .goto() method to stop after the first redirect happened and simply return the html from that first 3xx page when i call page.content() method?

You can enable a request interception and abort additional requests if a chain of requests is identified:
await page.setRequestInterception(true);
page.on('request', request => {
if (request.isNavigationRequest() && request.redirectChain().length !== 0) {
request.abort();
} else {
request.continue();
}
});
await page.goto('https://www.example.com/');

It seems that at the moment of writing, this is not possible (at least not in the high-level API that Puppeteer provides). Check out the docs for goto here.

Related

Cypress doesn't work with an external login

I'm working on e2e test with cypress on my application.
In my case the login are manage by a external service.
When I want to enter in my application's home page (https://myApplication/home), the system redirects me in different superdomains to login.
At first cypress seems to be able to change the superdomain, but once arrived in external service page for the authentication, the system go in login error (as if we have already logged in, but incorrect).
This type of behavior does not happen outside the cypress .
Are there alternative solutions to manage external access in a cypress test or is it possible to manage it directly from cypress?
I added in my cypress.json the chromeWebSecurity:false and when I call the link for login, I added the failOnStatusCode: false,
but it still doesn't work.
Assuming this is caused by SameSite cookie blocking , then I've just been fighting the same issue. I resolved it by intercepting all requests, checking if they had a set-cookie header(s) and rewriting the SameSite attribute. There's probably a neater way to do it, as this does clutter up the cypress dashboard a little.
Sadly Zachary Costa's answer no longer works as Chrome 94 removed the SameSiteByDefaultCookies flag.
You can add this as a command for easy reuse:
In your commands file:
declare namespace Cypress {
interface Chainable<Subject> {
disableSameSiteCookieRestrictions(): void;
}
}
Cypress.Commands.add('disableSameSiteCookieRestrictions', () => {
cy.intercept('*', (req) => {
req.on('response', (res) => {
if (!res.headers['set-cookie']) {
return;
}
const disableSameSite = (headerContent: string): string => {
return headerContent.replace(/samesite=(lax|strict)/ig, 'samesite=none');
}
if (Array.isArray(res.headers['set-cookie'])) {
res.headers['set-cookie'] = res.headers['set-cookie'].map(disableSameSite);
} else {
res.headers['set-cookie'] = disableSameSite(res.headers['set-cookie']);
}
})
});
});
Usage:
it('should login using third party idp', () => {
cy.disableSameSiteCookieRestrictions();
//add test body here
});
or alteratively, run it before each test:
beforeEach(() => cy.disableSameSiteCookieRestrictions());
We were encountering a similar issue, where Cypress was redirecting us to the default "You are not logged in" page after getting through the login process. I'm not certain if that's EXACTLY the issue you were experiencing, but just in case, here's our solution. In our case, the issue was caused by Chrome's "Same Site Cookies" feature interacting poorly with Cypress, so we needed to disable it. In your plugins/index.js file, you would add the following code:
module.exports = (on, config) => {
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-features=SameSiteByDefaultCookies');
}
return launchOptions;
});
};
Note that if you already have launchOptions being set, you can just add this code onto it so it doesn't clash at all.
Hopefully, this works for you as well!
In the current version of cypress you can't go to another domain in the same test. This is due to the fact that cypress injects its test into the browser (they are working on this issue).
So one solution today is that you need to utilize cy.request to perform the login programmatically and inject the auth secret (jwt, cookie, localstorage, token or what you have) into the browser context yourself (for cookie this would be cy.setcookie).
Always make sure to checkout the plugins if there is already an abstraction for your login. Often this is openId or ntlm.

Vue PWA caching routes in advance

I'm hoping someone can tell me if I'm barking up the wrong tree. I have built a basic web app using Vue CLI and included the PWA support. Everything seems to work fine, I get the install prompt etc.
What I want to do, is cache various pages (routes) that user hasn't visited before, but so that they can when offline.
The reason here is that I'm planning to build an app for an airline and part of that app will act as an in flight magazine, allowing users to read various articles, however the aircrafts do not have wifi so the users need to download the app in the boarding area and my goal is to then pre cache say the top 10 articles so they can read them during the flight.
Is this possible? and is PWA caching the right way to go about it? Has anyone does this sort of thing before?
Thanks in advance
To "convert" your website to an PWA, you just need few steps.
You need to know that the service worker is not running on the main thread and you cant access for example the DOM inside him.
First create an serviceworker.
For example, go to your root directory of your project and add a javascript file called serviceworker.js this will be your service worker.
Register the service worker.
To register the service worker, you will need to check if its even possible in this browser, and then register him:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/serviceworker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope');
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
In vue.js you can put this inside mounted() or created() hook.
If you would run this code it will say that the service worker is successfully registered even if we havent wrote any code inside serviceworker.js
The fetch handler
Inside of serviceworker.js its good to create a variable for example CACHE_NAME. This will be the name of your cache where the cached content will be saved at.
var CACHE_NAME = "mycache_v1";
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
Everytime you make a network request your request runs through the service worker fetch handler here first. You need to response with event.respondWith()
Next step is you first open your cache called mycache_v1 and take a look inside if there is a match with your request.
Remember: cache.match() wont get rejected if there is no match, it just returns undefined because of that there is a || operator at the return statement.
If there is a match available return the match out of the cache, if not then fetch() the event request.
In the fetch() you save the response inside the cache AND return the response to the user.
This is called cache-first approach because you first take a look inside the cache and in case there is no match you make a fallback to the network.
Actually you could go a step further by adding a catch() at your fetch like this:
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
})
.catch(err => {
return fetch("/offline.html")
});
In case there is nothing inside the cache AND you also have no network error you could response with a offline page.
You ask yourself maybe: "Ok, no cache available and no internet, how is the user supposed to see the offline page, it requires internet connection too to see it right?"
In case of that you can pre-cache some pages.
First you create a array with routes that you want to cache:
var PRE_CACHE = ["/offline.html"];
In our case its just the offline.html page. You are able to add css and js files aswell.
Now you need the install handler:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(PRE_CACHE);
})
);
});
The install is just called 1x whenever a service worker gets registered.
This just means: Open your cache, add the routes inside the cache. Now if you register you SW your offline.html is pre-cached.
I suggest to read the "Web fundamentals" from the google guys: https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook
There are other strategies like: network-first
To be honest i dont know exactly how the routing works with SPAs because SPA is just 1 index.html file that is shipped to the client and the routing is handled by javascript you will need to check it out witch is the best strategie for your app.

Handling GET 301/302 responses with JavaScript in Vue.js

I need to print some 301/302 response headers with JavaScript: I saw lots of answers for Angular, but I’m using Vue.js to get the results. Well, it doesn’t really matter since it’s mainly a JavaScript issue… I mean, I’m not just interested in a Vue.js-only solution, it could and should work everywhere. As a front-end developer, I can’t manually create a scenario in which my project returns a redirect in its own server and finding a random remote page that does it without CORS limitations is hard. To be even more clear, I need to get status code and statusText of 301/302 responses (as well as the Location of the new page) before the redirection, then the full headers of the redirected page. Now I can retrieve only these last, because the default this.$http.get(); request created by vue-resource doesn’t seem to store the first in an object. What I’m asking here is if there’s a way to store the redirection response in a variable too, then to show both. I don’t know if switching to axios could be preferrable — since I’m pretty new to Vue.js. My component’s method is as follow:
getRequest: function() {
this.$http.get(this.url)
.then(function(response) {
if (response.ok) {
if (response.status === 301 || response.status === 302) {
// show the redirecting response
}
// show the redirected response
}
}
EDIT: the sources for this project are on GitHub; when you send a GET request and the response says 301/302, you should be able to see three columns instead of two where the second shows details of the redirecting response headers.
You are not able no handle 301 or 302 status because those are inside the
if(response.ok)
block, which means that the status is 200. Try this instead:
getRequest: function() {
this.$http.get(this.url)
.then(function(response) {
if (response.ok) {
// show the redirected response
}
if (response.status === 301 || response.status === 302) {
// show the redirecting response
}
}

Redirect to previous url after login in nuxt.js

I basically want to redirect to the previous url when a user has successfully logged in.
I redirect to the login page with the previous url such as /login?redirect=/page1/page2.
And I want when a user authenticates to be redirected back to that url.
I am using the auth-module here: https://auth.nuxtjs.org/
How I login the user.
methods: {
async submit() {
await this.$auth.loginWith('local', {
data: this.form
})
}
}
The only thing that I could found in the docs is this: https://auth.nuxtjs.org/getting-started/options#redirect
which however only redirects to a specific page instead of the previous page in the query.
Any ideas on how to achieve this?
You Can Do This
this.$router.back()
And its go back to the last route.
Programmatic Navigation | Vue Router
https://router.vuejs.org/guide/essentials/navigation.html
Thanks.
There is a fairly detailed discussion in github about Nuxt having an issue with a redirect when you are hitting a protected page directly. The redirect goes to the default page redirect rather than the previously hit page. The correct behavior should be to store the redirect and then proceed to it after authentication (login) with correct credentials.
3 days ago (Apr 14, 2019), MathiasCiarlo submitted a PR on the auth-module repo to fix this. The base reason why the redirect was "lost" has to do with the state of the redirect value not being allowed to be set as a cookie in SSR mode. His code impacts the storage.js file, in particular the setCookie() method. I've included that changed method here just for reference.
setCookie (key, value, options = {}) {
if (!this.options.cookie) {
return
}
const _key = this.options.cookie.prefix + key
const _options = Object.assign({}, this.options.cookie.options, options)
if (isUnset(value)) {
Cookies.remove(_key, _options)
} else {
// Support server set cookies
if (process.server) {
this.ctx.res.setHeader('Set-Cookie', [_key + '=' + value])
} else {
Cookies.set(_key, value, _options)
}
}
return value
}
I've personally just altered my npm myself, but you could probably fork the repo and use that forked npm for the time being. Or you could wait until the PR is merged into the mainline of the auth-module repo.

Is it possible to trigger programmatically a ember mirage request response

I use Ember mirage in my tests. I need to check the state of my tested component after a request has been send but before the respond has been received.
How it is possible to configure my test to avoid the mirage server responds automatically and trigger the response programmatically?
I used to do that with sinonjs but I do not find the way to manage this use case with Ember mirage. It is possible?
http://www.ember-cli-mirage.com/docs/v0.3.x/route-handlers/
You can add a handler like this inside your test:
server.get('/users/:id', function(db, request) {
console.log(request) // to debug request/response
return db.users.find(request.params.id);
});
If I understand your question correctly, you are trying to test a situation on the page (acceptance test) when data were sent to the server but the response still did not arrive.
It is possible to access server instance in your test, so it is not that complicated to create your own method that will pause/resume responding but the simpler option (that I use as well) is just to postpone response from mirage using timing option (http://www.ember-cli-mirage.com/docs/v0.3.x/configuration/#timing). Then, when you do your tests before andThen() you should be in a situation that you wish to test.
you can access the underlying pretender instance and the fact that mirage just passes the timing parameter straight through to the pretender request.
https://github.com/pretenderjs/pretender#timing-parameter
Unfortunately pretender doesn't have docs for requestReferences and requiresManualResolution(verb, path), but this helper function will process all outstanding manual requests
function resolveManualPretenderRequests(pretender) {
pretender.requestReferences.forEach((ref) => {
if (pretender.requiresManualResolution(ref.request.method, ref.request.url)) {
pretender.resolve(ref.request);
}
});
}
Then you can just use mirage to register a manual request handler
server.get('/models:id', { timing: true });
so in an example test, you can use the ember test helper waitFor() to do something like
test('button is disabled while loading', async function(assert) {
assert.expect(2);
// passing true to timing tells the underlying pretender handler wait for the request to be manually processed
server.get('/models/:id', { timing: true });
// await render will wait for promises to settle, but we actually don't want that
const renderPromise = render(hbs`<MyComponent />`);
// the waitFor() helper instead will allow us to just wait for our button to render
await waitFor('button');
const button = this.element.querySelector('button');
// since the request has not resolved yet, the button is disabled
assert.strictEqual(button.disabled, true);
// then we manually resolve the request
resolveManualPretenderRequests(server.pretender);
// now we can await the render so that we get our updated button state
await renderPromise;
// with the request resolved, now the button is no longer disabled
assert.strictEqual(button.disabled, false);
});