Disable synchronization before launchApp - detox

Is it possible to disable synchronization before/during launchApp (with newInstance: true)? Ideally I'd like the:
await device.launchApp({ newInstance: true, url });
to resolve immediately.
I've inherited an app that does weird things at launch, so I'd like to bypass synchronization at the beginning and only reenable it afterwards.
I tried something like this:
await device.disableSynchronization();
await device.launchApp({ newInstance: true, url });
await waitFor(element(by.id('root'))).toBeVisible().withTimeout(10000);
await device.enableSynchronization();
but from the docs I read that synchronization is always re-enabled for new instances.
Is there a way to force synchronization to be off so that device.launchApp can actually resolve?

This is now possible using the launch argument -detoxEnableSynchronization NO.
See the documentation here:
https://github.com/wix/Detox/blob/master/docs/APIRef.DeviceObjectAPI.md#10-detoxenablesynchronizationinitialize-detox-with-synchronization-enabled-or-disabled-at-app-launch
Old answer:
Detox does not support disabling synchronization on launch, but if a network request is causing issues, you can pass a URL blacklist as a launch argument, which will disable synchronization for that network request.
await device.launchApp({
newInstance: true,
launchArgs: { detoxURLBlacklistRegex: ' \\("http://192.168.1.253:19001/onchange","https://e.crashlytics.com/spi/v2/events"\\)' },
});
https://github.com/wix/Detox/blob/master/docs/APIRef.DeviceObjectAPI.md#10-initialize-the-url-blacklist-at-device-launch

Related

cy.origin redirects user to a blank page

Scenario:
I am clicking a login button from my application served on localhost.
It redirects me to azure sso login through cy.origin
Authentication is performed fine.
User logs in successfully to the app.
But it redirects me to a blank page and hence rest of the IT blocks get failed.
The code attached below works fine but as soon as first IT block passes, upon the execution of second IT block page is set to about:blank so the test cases fail.
Question: What should be the workaround so that I can continue testing on application under test?
Second describe gets failed
Cypress.Commands.add('authenticate', () =>{
cy.visit('http://localhost:8080/')
cy.get('input[value="***"]').click();
cy.origin(`https://login.microsoftonline.com/`, () => {
cy.wait(3000)
cy.get('#i0116').type('username')
cy.get('#idSIButton9').click()
cy.wait(3000)
cy.get('#i0118').type('password')
cy.wait(2000)
cy.get('#idSIButton9').click()
cy.wait(2000)
cy.get('#idSIButton9').click();
})
cy.wait(6000)
cy.url().should('contain', 'Welcome')
})
According to the documentation, that behavior is by design
Take a look at cy.origin()
The cy.origin() command is currently experimental and can be enabled by setting the experimentalSessionAndOrigin flag to true in the Cypress config.
Enabling this flag does the following:
It adds the following new behaviors (that will be the default in a future major version release of Cypress) at the beginning of each test:
The page is cleared (by setting it to about:blank).
If by "Second describe gets failed" you mean the second test is not visiting the Welcome page, then just explicitly visit cy.visit('http://localhost:8080/') at the beginning of the second test.
This is the recommended approach when using cy.origin.
By the way, you should set http://localhost:8080/ as baseUrl in configuration, and use cy.visit('/') instead - from Cypress best practices.
Cypress.Commands.add("session_thing", (email, password) => {
cy.session([email, password], () => {
cy.visit('http://localhost:8080/AdminWebapp/Welcome.html')
cy.get('input[value="Log In With Office 365"]').click();
cy.origin(
`https://login.microsoftonline.com/`,
{ args: [email, password] },
([email, password]) => {
cy.wait(3000)
cy.get('#i0116').type(email)
cy.get('#idSIButton9').click()
cy.wait(3000)
cy.get('#i0118').type(password)
cy.wait(2000)
cy.get('#idSIButton9').click()
cy.wait(2000)
cy.get('#idSIButton9').click();
}
);
cy.url().should('contain', 'Welcome')
});
});
The desired behavior was achieved with above code. It restores the session in beforeEach hook. I am simply calling the cy.visit('/') in every IT block and perform the required actions which is kind of very fast with session feature.

How to use Puppetter with express API and properly closing the browser without affecting other concurrent request

I have a website which has some data as HTML (loaded via ajax) and I have to convert that to JSON with custom formatting.
So, for this I choose Puppeteer.
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const page = await browser.newPage();
This web API that I'm developing will be having concurrent web requests, so I thought browser.close() might affect the other concurrent requests, so I decide to do only page.close.
One problem that I'm facing is when I do puppeteer.launch, for each request it opens two about:blank tab in a new window.
And when browser.newPage() is requested it returns one of the blank tabs and leaves the other one opened.
That leads to multiple window opened with multiple about:blank.
Here I don't know the right way to handle this, I can't close the browser because it will close all the pages which are being used by other requests.
You are seeing an empty (about:blank) tab each time you run this code, because you are doing two things here:
Launching a new browser - which already starts with an open tab
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
Opening a new tab.
const page = await browser.newPage();
If you don't want to have "zombie" blank tabs, then you can just reuse the initial tab like this:
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
const currentPages = await browser.pages(); // list the opened tabs
const [page] = currentPages; // use the first (and only) opened tab.
Note that in this case, since you are just reusing the only one and initial tab, closing it with page.close() will have the same effect as closing the browser with browser.close().
Exploring some Express + Browser concurrency alternatives
Consider that a different solution would take place if you want to reuse the same browser instance for the lifetime of your Express server, ie. serve all requests on the same browser, or if you want to use a new browser instance for each individual request.
1. One browser instance per server
In this case it might make sense, depending on your requirements, in managing one tab per request.
// launch the browser instance, once
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// handle incoming requests
app.get("/foo", async (req, res) => {
const page = await browser.newPage();
try {
// ... execute some logic on this new page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the opened tab, no matter how the logic resulted
await page.close()
}
})
Note that still in this scenario, the browser context would be shared across the pages, for example cookies, local storage, and so on. You have to consider this if you plan to allow concurrent requests that also can have conflicts in reusing the same shared context.
2. One browser instance per request
In this scenario you launch a new browser instance per request, you ensure each request will have a clean context and won't collide with other possible requests.
app.get("/foo", async (req, res) => {
// launch the browser instance, one per request
const browser = await puppeteer.launch({
headless: true, args: ['--no-sandbox']
});
// no need to open a new tab, reuse the first one
const [page] = await browser.pages();
try {
// ... execute some logic on the page
} catch(error) {
// whoops, logic went wrong, respond with 500 or something
} finally {
// cleanup: close the browser
// await page.close() // (not really needed if you will close the entire browser,
// and would have the same effect as browser.close()
// if you haven't opened more tabs)
await browser.close()
}
})
But consider that spining a new browser process up would also be more resource-intensive, and your request would take more time to resolve, compared to reusing an already available browser process.
EDIT: code formatting.

pouchdb + vuex update live with changes

I have an app that takes updates into VUEX store and syncs those change from pouchdb to couchdb. Which is great but now I need to have two clients connected and see the change in near realtime.
So I have the https://pouchdb.com/guides/changes.html API which I can use to listen for changes to the DB and when that happens call a action which mutates the vuex state on Client 2. Code below.
However the bit I cannot seem to work out is this code is not just listening all the time ? So where should I put this in Vue to ensure that it hears any changes. I can call it when I make a state change and I see that it hears the change but ofcourse I want to trigger a state change on client 2, without them having to make change. Do I need a timer ? The pouch docs seem to suggest this changes api should be able to update UI based on a change to the data, which I can probably call with a button press to check for changes ...but I want to listen in near realtime ?
pouchdb
.changes({
since: 'now',
include_docs: true
})
.on('change', function(change) {
// received a change
store.commit('CHANGE_STATE', change.doc.flavour)
})
.on('error', function(err) {
// handle errors
console.log(err)
})
Your explanation is a bit fuzzy in that you talk about client 2 without ever mentioning client 1. I assume client 2 is a passive listener and client 1 is where data is changed. If I remember correctly from when I was building my Vue / PouchDB project last year I looked into how to coordinate the Store and the Database, and then thought, "Why bother? They're just two kinds of local storage". As long as changes in client 1 replicate to your Couch server and client 2 detects those server side changes and writes them into reactive variables, they'll propagate to the UI.
I used replicate.to() for storing client-side changes and replicate.from() to detect server-side changes. The replicate() functions have their own timer, constantly monitoring the changes queue, so you don't need to roll your own.
This is what I ended up doing !
actions: {
SYNC_DB() {
// do one way, one-off sync from the server until completion
pouchdb.replicate.from(remote).on('complete', function(info) {
// then two-way, continuous, retriable sync
pouchdb
.sync(remote, { live: true, retry: true })
.on('change', function(info) {
store.commit('CHANGE_STATE', info.change.docs[0].flavour)
})
.on('paused', function(err) {
// replication paused (e.g. replication up to date, user went offline)
})
.on('active', function() {
// replicate resumed (e.g. new changes replicating, user went back online)
})
.on('denied', function(err) {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function(info) {
// handle complete
})
.on('error', function(err) {
// handle error
})
})
},

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);
});

How to perfectly isolate and clear environments between each test?

I'm trying to connect to SoundCloud using CasperJS. What is interesting is once you signed in and rerun the login feature later, the previous login is still active. Before going any further, here is the code:
casper.thenOpen('https://soundcloud.com/', function() {
casper.click('.header__login');
popup = /soundcloud\.com\/connect/;
casper.waitForPopup(popup, function() {
casper.withPopup(popup, function() {
selectors = {
'#username': username,
'#password': password
};
casper.fillSelectors('form.log-in', selectors, false);
casper.click('#authorize');
});
});
});
If you run this code at least twice, you should see the following error appears:
CasperError: Cannot dispatch mousedown event on nonexistent selector: .header__login
If you analyse the logs you will see that the second time, you were redirected to https://soundcloud.com/stream meaning that you were already logged in.
I did some research to clear environments between each test but it seems that the following lines don't solve the problem.
phantom.clearCookies()
casper.clear()
localStorage.clear()
sessionStorage.clear()
Technically, I'm really interested about understanding what is happening here. Maybe SoundCloud built a system to also store some variables server-side. In this case, I would have to log out before login. But my question is how can I perfectly isolate and clear everything between each test? Does someone know how to make the environment unsigned between each test?
To clear server-side session cache, calling: phantom.clearCookies(); did the trick for me. This cleared my session between test files.
Example here:
casper.test.begin("Test", {
test: function(test) {
casper.start(
"http://example.com",
function() {
... //Some testing here
}
);
casper.run(function() {
test.done();
});
},
tearDown: function(test) {
phantom.clearCookies();
}
});
If you're still having issues, check the way you are executing your tests.
Where did you call casper.clear() ?
I think you have to call it immediately after you have opened a page like:
casper.start('http://www.google.fr/', function() {
this.clear(); // javascript execution in this page has been stopped
//rest of code
});
From the doc: Clears the current page execution environment context. Useful to avoid having previously loaded DOM contents being still active.