Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: premature resolution of wait conditions when using scopedBy page objects #326

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

epfremmer
Copy link
Contributor

When using things like await this.users.scopeByName('some name').waitForVisible(); the promise immediately rejects because the scoped selector cannot be found.

Example error:

waitForVisible: waitForVisible(<func>): findChild([data-cs-selector="user-row"],<func>): scopeBy([data-cs-selector="user-row"],<func>): Could not find match (length 19)

@kellyselden
Copy link
Collaborator

Travis CI has been really bad lately. It's not picking up new builds.

@epfremmer
Copy link
Contributor Author

epfremmer commented Jun 19, 2020

Is this because this is on a fork? Did a force push and seems to have picked it up that time

@epfremmer epfremmer force-pushed the fix-wait-for-scoped-page-object branch 6 times, most recently from 1bf8a3c to c5f670a Compare June 19, 2020 20:10
@epfremmer
Copy link
Contributor Author

epfremmer commented Jun 19, 2020

@kellyselden finally got this to behave 🎉

try {
return await this._browser.waitUntil(async () => await this.isExisting());
} catch (e) {
// if the waitUntil condition fails fall through to the original non-scoped
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will unfortunately double the waiting time of failing tests, right? 30 secs to 60 secs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default timeout is still 30 seconds. I tested this by commenting out all of the timeout overrides in one of the tests and allowing it to fail, which it did after 30 seconds


await this.open('index.html');

this.page._browser._browser.options.waitforTimeout = 10000;
Copy link
Collaborator

@kellyselden kellyselden Jun 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The browser is global, this will leak across the whole test suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a timeout value reset in afterEach hook

element.disabled = false;

document.body.appendChild(element);
}, 100);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has a race condition. If the assertion runs before this, the test will fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional I want scopeBy to start running and respond to the element changing state in order to test that the wait condition is working.

@@ -41,6 +41,16 @@ class BaseElement extends BasePageObject {

async waitForInsert() {
await handleError.call(this, async () => {
if (typeof this._selector === 'function') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These can go in a reusable function, kinda like handleError so they both can use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you want it called?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitForScoped?

@@ -41,6 +41,16 @@ class BaseElement extends BasePageObject {

async waitForInsert() {
await handleError.call(this, async () => {
if (typeof this._selector === 'function') {
try {
return await this._browser.waitUntil(async () => await this.isExisting());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this go in the browser's waitForExist implementation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that first and ran into so many issues because of all of the areas in which waitForExists impacts. Opted to hoist it up to here for simplicity sake.

@@ -12,10 +12,12 @@ class Element extends BaseElement {
}

async waitForEnabled() {
await this.waitForInsert();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these are necessary. The existing assumption is that you are already dealing with an existing element when you call these methods. You are waiting for an element to go from disabled to enabled. If you're element is missing to begin with, the consumer should be tasked with calling waitForInsert first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like it is better developer ergonomics for consumers to not have to be concerned with that, as well as, fewer wait condition lines in tests.

I'll remove this, but I think we should consider putting it back in to make for a smoother testing experience.

@epfremmer epfremmer force-pushed the fix-wait-for-scoped-page-object branch from c5f670a to 0e77136 Compare June 22, 2020 18:35
Edward Pfremmer added 2 commits June 22, 2020 13:38
When using a function style selector wait conditions fail early if the
scoped selector does not yield any results. Instead we should continue
to wait/check until the wait condition times out.

Additionally in order to retain custom friendly error messages that
include the selector that an exception occurred on we fallback to the
original wait behavior which includes the custom errors.
before waiting on other conditions like enabled/visible
@epfremmer epfremmer force-pushed the fix-wait-for-scoped-page-object branch from 0e77136 to d68f9aa Compare June 22, 2020 18:38
@@ -41,6 +41,16 @@ class BaseElement extends BasePageObject {

async waitForInsert() {
await handleError.call(this, async () => {
if (typeof this._selector === 'function') {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

waitForScoped?

if (this.browser && this.browser.options) {
// some of the tests need to validate wait conditions and modify the wait
// timeout so this needs to be reset to avoid leaking context between tests
this.browser.options.waitforTimeout = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should check out isExisting. It would be better to not assume any hard coded numbers here.

await this.writeFixture('index.html', `
<input class="foo">
`);
describe(Element.prototype.waitForEnabled, function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the waiting in these methods was removed, I don't think any of these extra tests are needed anymore.

@@ -56,6 +66,16 @@ class BaseElement extends BasePageObject {

async waitForDestroy() {
await handleError.call(this, async () => {
if (typeof this._selector === 'function') {
try {
return await this._browser.waitUntil(async () => !(await this.isExisting()));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return value is not used in this method.


await this.open('index.html');

this.page._browser._browser.options.waitforTimeout = 10000;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you think 1 second is enough?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10000 = 10,000 = 10 seconds, no?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, I'm proposing switching to 1000, and also asking if that would be enough.

.to.eventually.be.fulfilled;
});

it('waits for scoped element to be inserted', async function() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe putting these in their own describe block would be better? not sure, but then you could set the new timeout in the beforeEach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants