I would like to test my "waiting for server reply" UI behavior. How do I do that reliably without pausing my test for a hardcoded delay?
Say, I have a button that triggers an http request and has to show certain animation / actions until the response arrives.
A dumb working method is:
cy.route({
delay: 1000,
response: "blah blah",
})
// triggger submission
cy.get('#my-submit-button').click()
// will disappear upon receiving response
cy.contains('Waiting for response...')
I am pretty much sure that the "waiting" text will appear within a second while the response paused, but then I commit the sin of pausing the test for a whole second.
If I start to shorten or remove the delay then I am running a risk of creating flaky tests since there's a chance the response would be processed before I check for the existence of the "Waiting..." text, which would've been removed by that moment.
Is there a way to ensure the response is produced only after the check for the "Waiting..." text without a hard delay?
I naïvely tried to do a cypress assertion from the route's onResponse, but cypress wasn't happy about that:
cy.route({
onResponse: xfr => {
cy.contains('Waiting for response...')
return xfr
},
response: "blah blah",
})
cy.get('#my-submit-button').click()
producing the https://on.cypress.io/returning-promise-and-commands-in-another-command error:
Error: Uncaught CypressError: Cypress detected that you returned a promise from a command while also invoking one or more cy commands in that promise.
The command that returned the promise was:
> cy.click()
The cy command you invoked inside the promise was:
> cy.contains()
Because Cypress commands are already promise-like, you don't need to wrap them or return your own promise.
Cypress will resolve your command with whatever the final Cypress command yields.
The reason this is an error instead of a warning is because Cypress internally queues commands serially whereas Promises execute as soon as they are invoked. Attempting to reconcile this would prevent Cypress from ever resolving.
Could this work?
cy.route({
delay: 1000,
response: "blah blah",
}).as('getResponse')
cy.wait('getResponse')
https://docs.cypress.io/guides/guides/network-requests.html#Waiting
You could record the time these things happen using cy.moment(), then compare it afterwards using .isBefore() or one of the other moment functions.
I'm using onResponse here to record the time the response comes in.
let timeDisappeared;
let timeResponded;
cy.route({
delay: 1000,
response: "blah blah",
onResponse: () => {
timeResponded = Cypress.moment()
}
})
// triggger submission
cy.get('#my-submit-button').click()
// waits for the text to appear
cy.contains('Waiting for response...').should('exist')
// waits for the text to disappear
cy.contains('Waiting for response...').should('not.exist').then(() => {
timeDisappeared = Cypress.moment()
})
expect(timeResponded.isBefore(timeDisappeared))
After saw your comment and check the other answers
I think Bredan's answer is the best solution to do this. You can
stub a response with delay: x seconds
check the text to appear. Record the time: timeAppear
check the text to disappear. Record the time: timeDisappear
Check if timeDisappear - timeAppear > x seconds
or Check timeDisappear - timeAppear - x seconds > certain seconds(You can define the tolerance)
This proves the text shows in the given response time.
You can extend the response Delay to longer value.
I modified Bredan's answer a bit to reflect the steps above,
let timeAppear;
let timeDisappear;
//delay the response to come back after 20 seconds
cy.route({
delay: 20000,
response: "blah blah"
})
// triggger submission
cy.get('#my-submit-button').click()
// waits for the text to appear and record the time starts.
cy.contains('Waiting for response...').should('exist').then(() => {
timeAppear = Cypress.moment()
})
// waits for the text to disappear and record the time ends
// add timeouts option here to make sure it is longer than delay
cy.contains('Waiting for response...',{timeout:30000}).should('not.exist').then(() => {
timeDisappear = Cypress.moment();
//check the time is above the delay time 2000 ms
expect(timeDisappear - timeAppear).to.be.above(20000);
//or check the tolerance value e.g.5000 ms
expect(timeDisappear - timeAppear - 20000).to.be.below(5000);
})
Related
In my scenario, I am polling a specific url, let's say http://example.com/search.
The response code returned is 202, and when the API has finished processing the task, it will eventually return status code 200.
My question is how do I assert that I eventually got status code 200, when the URL is the same for both the 202 and 200 responses?
Thanks,
It looks like you can to set times property to 2 and then wait two times:
cy
.intercept('/search', { times: 2 })
.as('search');
// ...
cy
.wait('#search')
.its('response.statusCode')
.should('eq', 200);
cy
.wait('#search')
.its('response.statusCode')
.should('eq', 202);
Every time a Protractor element locator fails, it prints an error and continues down a horrible path of endless cascading failures in my spec and suite. Every test that follows depends on the element locator finding its element, and depends on the current spec passing.
I would like to keep the web page under test open while I use the console. The goal is to debug the current state of the page and investigate why the element locator may have failed to find its target.
I'm not too concerned about failing the entire suite and exiting on the first spec failure (I've seen other answers on --fail-fast and stopping on first spec failure.) This is not the approach I would like to take. I want to set a breakpoint, and inspect the environment while the page is running.
Maybe there's something like a Jasmine option for doThisOnFailure: () => { debugger }, which would work for me I think.
I really do not like the solution of using a spec reporter to execute during afterEach and check the failed spec count on the Jasmine environment for the entire spec function. I want to immediately know when an element locator has failed and immediately break as soon as it has failed.
Maybe something really gross would work $('element').click().catch(() => { debugger }).
EDIT: Please, note that I am asking about breaking in a spec, not breaking at the end of the spec.
it('should execute deadly code', function () {
p.navigation.openStorageConfigTab()
$$('.bad-selector').get(0).click() /* IMPORTANT: I want to break here */
p.volume.navigateTo()
})
it('should not execute this spec', function () {
$$('.bad-selector').get(0).click()
})
And the output
✗ should execute deadly code
- Failed: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator By(css selector, .bad-selector)
✗ should not execute this spec
- Failed: Index out of bound. Trying to access element at index: 0, but there are only 0 elements that match locator By(css selector, .bad-selector)
I can recommend you the approach I use, and I hope you can take it from here
Overall approach is to wait until until you type close/ command in browser url:
await browser.waitForAngularEnabled(false);
await browser.wait(
async () => {
let url = await browser.getCurrentUrl();
return url.includes('close/');
},
5 * 60 * 1000,
'Keep-alive timeout reached, closing the session...'
);
The question is when you want to call it. I use the advantage of onComplete callback function in config file. When it's called, the browser is still available. So once all tests are completed, it doesn't exit for 5 minutes unless I submit close/ to the url field. Obviously that can be conditional, by adding something like if (DEBUG === true)
A downside of this setup is it's called when all tests are completed, and it's possible your spec has navigated away from the page where there was error. So what you can also do is to use advantage of jasmine reporter (if you use jasmine). Roughly, you just need to add this to your onPrepare func:
jasmine.getEnv().addReporter({
jasmineStarted: function(suiteInfo) {},
suiteStarted: function(result) {},
specStarted: function(result) {},
specDone: async function(spec) {
if (spec.status === 'failed') {
await browser.waitForAngularEnabled(false);
await browser.wait(
async () => {
let url = await browser.getCurrentUrl();
return url.includes('close/');
},
5 * 60 * 1000,
'Keep-alive timeout reached, closing the session...'
);
await browser.close();
process.exit(35);
}
},
suiteDone: function(result) {},
jasmineDone: function(result) {},
});
So if any it block has failed status, then it'll stop. BUT, I have not tested it, I'll leave it up to you. And second, I didn't think about what will happen to the rest of queued specs since you're redirected to non existing url close/, but I believe it'll still work for you. Worst case scenario, you can play around and make it continue or close the browser instance, as long as you understood the concept
P.S.
I modified the code to close the browser when you type close/, by adding
await browser.close();
process.exit(35);
I tested this code with the following scenarios:
happy path: all 5 it are successful
first element finder of second it block fails
second element finder of second it block fails
All passed. The code works as expected
I am using JMeter Webdriver sampler for the application UI response time measurement. I am facing issue with the wait function. For example the login page loads between 10 to 120 secs. So I have the following code for the login page in Webdriver sampler for the page load check.
var ui=JavaImporter(org.openqa.selenium.support.ui)
var wait=new support_ui.WebDriverWait(WDS.browser,120)
wait.until(ui.ExpectedConditions.visibilityOfElementLocated(pkg.By.className('logout-btn-hover')))
The issue is even after the page load completely, JMeter is still waiting to perform the next action. This waiting time will reduce if I reduce the 120 secs.But sometimes the application will take 120 secs to load also so I need to keep 120 secs.
I am writing the time to a log file once the sampler completes its action. Because of the wait time issue I am not able to calculate time properly.
There are at least 2 errors in your script, it should look something like:
var ui=JavaImporter(org.openqa.selenium.support.ui)
var wait=new ui.WebDriverWait(WDS.browser,120)
wait.until(ui.ExpectedConditions.visibilityOfElementLocated(org.openqa.selenium.By.className('logout-btn-hover')))
Check out jmeter.log file for any suspicious entries, in particular for something like:
ERROR c.g.j.p.w.s.WebDriverSampler: Expected condition failed: waiting for visibility of element located by By.className: logout-btn-hover (tried for 120 second(s) with 500 milliseconds interval)
Double check your CSS selector
Consider refactoring your code to look for the logout button(?) in a loop with verbose logging for each step. Sample code:
var pkg = JavaImporter(org.openqa.selenium)
WDS.sampleResult.sampleStart()
WDS.browser.get('http://example.com')
var start = new Date().getTime()
var attempt = 1
while (new Date().getTime() - start < 5000) {
try {
var logout = WDS.browser.findElement(pkg.By.className('logout-btn-hover'))
WDS.log.info('Element found')
break
}
catch (err) {
WDS.log.info('Attempt # ' + attempt + ', Element not found')
java.lang.Thread.sleep(1000)
attempt++
}
}
WDS.sampleResult.sampleEnd()
Example output when the element is not found:
Example output when the element is found:
Check out The WebDriver Sampler: Your Top 10 Questions Answered article for more information on using WebDriver sampler in JMeter scripts.
I want to ensure that a given API call is throttled so that for a given time interval, only a single request is fired, and that the other, throttled requests wait and receive the results of the request that was actively fired
Example
const generateReport = (args) => client.get(...)
const generateReportA = (argsForA) =>
generateReport(argsForA).then(saveReportSomewhere)
const generateReportB = (argsForB) =>
generateReport(argsForB).then(saveReportSomewhere)
const generateReportC = (argsForC) =>
generateReport(argsForC).then(saveReportSomewhere)
If we then run the statements below
generateReportA(...).then(console.log) // should log result of C
generateReportB(...).then(console.log) // should log result of C
generateReportC(...).then(console.log) // should log result
right after each other, I only want to fire the request associated with generateReportC and I'd like both generateReportA and generateReportB to receive and handle the result of generateReportC.
In the end generateReport should have been called once and saveReportSomewhere should have been called 3 times, each with the result from generateReportC
Is this possible?
This will fire C request get result and save it, then trigger A and B at the same time with result from C and save results immediately after every emit.
generateReportC.pipe(
tap(cResult => saveReportSomewhere(cResult)),
mergeMap(cResult => merge(generateReportA(cResult), generateReportB(cResult))),
tap(result => saveReportSomewhere(result))
).subscribe();
Consider a blocking function: this_thread::sleep_for(milliseconds(3000));
I'm trying to get the following behavior:
Trigger Blocking Function
|---------------------------------------------X
I want to trigger the blocking function and if it takes too long (more than two seconds), it should timeout.
I've done the following:
my_connection = observable<>::create<int>([](subscriber<int> s) {
auto s2 = observable<>::just(1, observe_on_new_thread()) |
subscribe<int>([&](auto x) {
this_thread::sleep_for(milliseconds(3000));
s.on_next(1);
});
}) |
timeout(seconds(2), observe_on_new_thread());
I can't get this to work. For starters, I think s can't on_next from a different thread.
So my question is, what is the correct reactive way of doing this? How can I wrap a blocking function in rxcpp and add a timeout to it?
Subsequently, I want to get an RX stream that behaves like this:
Trigger Cleanup
|------------------------X
(Delay) Trigger Cleanup
|-----------------X
Great question! The above is pretty close.
Here is an example of how to adapt blocking operations to rxcpp. It does libcurl polling to make http requests.
The following should do what you intended.
auto sharedThreads = observe_on_event_loop();
auto my_connection = observable<>::create<int>([](subscriber<int> s) {
this_thread::sleep_for(milliseconds(3000));
s.on_next(1);
s.on_completed();
}) |
subscribe_on(observe_on_new_thread()) |
//start_with(0) | // workaround bug in timeout
timeout(seconds(2), sharedThreads);
//skip(1); // workaround bug in timeout
my_connection.as_blocking().subscribe(
[](int){},
[](exception_ptr ep){cout << "timed out" << endl;}
);
subscribe_on will run the create on a dedicated thread, and thus create is allowed to block that thread.
timeout will run the timer on a different thread, that can be shared with others, and transfer all the on_next/on_error/on_completed calls to that same thread.
as_blocking will make sure that subscribe does not return until it has completed. This is only used to prevent main() from exiting - most often in test or example programs.
EDIT: added workaround for bug in timeout. At the moment, it does not schedule the first timeout until the first value arrives.
EDIT-2: timeout bug has been fixed, the workaround is not needed anymore.