Skip to content

Commit

Permalink
feat(web): PageElement-releated expectations now also check if the el…
Browse files Browse the repository at this point in the history
…ement is present

Expectations that a PageElement isActive, isClickable, isEnabled, isSelected, or isVisible will now assume that if the element is not present, it cannot therefore be active, clickable, enabled, selected, or visible, respectively.
This should be reasonable and intuitive behaviour in most cases. However, note that if in your scenario you want to ensure that the element is, for example, "not enabled", and there is a possibility for the element to not be there at all, you might want to compose expectations as follows:

```
Ensure.that(element, and(isPresent(), not(isEnabled()))
```

This way you prevent an error that might be caused by `not(isEnabled())` returning `true` when the element is also not present.

Another thing to note is that this change will make it easier for you to perform expectations on PageElements that might not return an element.

For example, the following code will now work:
```
const listItems = PageElements.located(By.css('li'))

Wait.until(listItems.first(), isVisible())
```

Before this change, you had to make sure you checked if the element was also present to avoid the test throwing an error when there were no elements returned:
```
const listItems = PageElements.located(By.css('li'))

Wait.until(listItems.first(), and(isPresent(), isVisible()))
```

closes #1255
  • Loading branch information
jan-molak committed Jul 22, 2022
1 parent 58f45fe commit de4610c
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 95 deletions.
64 changes: 42 additions & 22 deletions integration/web-specs/spec/expectations/isActive.spec.ts
Expand Up @@ -3,49 +3,69 @@ import 'mocha';
import { expect } from '@integration/testing-tools';
import { Ensure, not } from '@serenity-js/assertions';
import { actorCalled, AssertionError, Wait } from '@serenity-js/core';
import { By, Click, isActive, Navigate, PageElement } from '@serenity-js/web';
import { By, Click, isActive, Navigate, PageElement, PageElements } from '@serenity-js/web';

describe('isActive', function () {

const Page = {
activeInput: PageElement.located(By.id('active')).describedAs('the active input'),
autofocusedInput: PageElement.located(By.id('autofocused')).describedAs('the autofocused input'),
inactiveInput: PageElement.located(By.id('inactive')).describedAs('the inactive input'),
const Elements = {
nonExistent: () => PageElement.located(By.id('does-not-exist')).describedAs('non-existent element'),
nonExistentElements: () => PageElements.located(By.id('does-not-exist')).describedAs('non-existent elements'),
activeInput: () => PageElement.located(By.id('active')).describedAs('the active input'),
autofocusedInput: () => PageElement.located(By.id('autofocused')).describedAs('the autofocused input'),
inactiveInput: () => PageElement.located(By.id('inactive')).describedAs('the inactive input'),
};

beforeEach(() =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/expectations/is-active/active_inactive_inputs.html'),
));

/** @test {isActive} */
it('allows the actor flow to continue when the element is active', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Page.autofocusedInput, isActive()),
Ensure.that(Page.autofocusedInput, isActive()),
describe('resolves to true when the element', () => {

Ensure.that(Page.inactiveInput, not(isActive())),
Click.on(Page.inactiveInput),
/** @test {isActive} */
it('is active', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Elements.autofocusedInput(), isActive()),
Ensure.that(Elements.autofocusedInput(), isActive()),

Wait.until(Page.inactiveInput, isActive()),
Ensure.that(Page.inactiveInput, isActive()),
)).to.be.fulfilled);
Ensure.that(Elements.inactiveInput(), not(isActive())),
Click.on(Elements.inactiveInput()),

/** @test {isActive} */
it('breaks the actor flow when element is inactive', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Page.inactiveInput, isActive()),
)).to.be.rejectedWith(AssertionError, `Expected the inactive input to become active`));
Wait.until(Elements.inactiveInput(), isActive()),
Ensure.that(Elements.inactiveInput(), isActive()),
)).to.be.fulfilled);
});

describe('resolves to false when the element', () => {

/** @test {isActive} */
it('is inactive', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.inactiveInput(), isActive()),
)).to.be.rejectedWith(AssertionError, `Expected the inactive input to become active`));

/** @test {isActive} */
it('does not exist', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistent(), isActive()),
)).to.be.rejectedWith(AssertionError, `Expected non-existent element to become present`));

/** @test {isActive} */
it('does not exist in a list of PageElements', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistentElements().first(), isActive()),
)).to.be.rejectedWith(AssertionError, `Expected the first of non-existent elements to become present`));
});

/** @test {isActive} */
it('contributes to a human-readable description of an assertion', () => {
expect(Ensure.that(Page.activeInput, isActive()).toString())
expect(Ensure.that(Elements.activeInput(), isActive()).toString())
.to.equal(`#actor ensures that the active input does become active`);
});

/** @test {isActive} */
it('contributes to a human-readable description of a wait', () => {
expect(Wait.until(Page.activeInput, isActive()).toString())
expect(Wait.until(Elements.activeInput(), isActive()).toString())
.to.equal(`#actor waits up to 5s, polling every 500ms, until the active input does become active`);
});
});
22 changes: 18 additions & 4 deletions integration/web-specs/spec/expectations/isClickable.spec.ts
Expand Up @@ -3,14 +3,16 @@ import 'mocha';
import { expect } from '@integration/testing-tools';
import { Ensure } from '@serenity-js/assertions';
import { actorCalled, AssertionError, Wait } from '@serenity-js/core';
import { By, isClickable, Navigate, PageElement } from '@serenity-js/web';
import { By, isClickable, Navigate, PageElement, PageElements } from '@serenity-js/web';

describe('isClickable', function () {

const Elements = {
enabledButton: () => PageElement.located(By.id('enabled')).describedAs('the enabled button'),
disabledButton: () => PageElement.located(By.id('disabled')).describedAs('the disabled button'),
hiddenButton: () => PageElement.located(By.id('hidden')).describedAs('the hidden button'),
nonExistent: () => PageElement.located(By.id('does-not-exist')).describedAs('non-existent element'),
nonExistentElements: () => PageElements.located(By.id('does-not-exist')).describedAs('non-existent elements'),
enabledButton: () => PageElement.located(By.id('enabled')).describedAs('the enabled button'),
disabledButton: () => PageElement.located(By.id('disabled')).describedAs('the disabled button'),
hiddenButton: () => PageElement.located(By.id('hidden')).describedAs('the hidden button'),
};

beforeEach(() =>
Expand Down Expand Up @@ -41,6 +43,18 @@ describe('isClickable', function () {
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.hiddenButton(), isClickable()),
)).to.be.rejectedWith(AssertionError, `Expected the hidden button to become visible`));

/** @test {isClickable} */
it('does not exist', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistent(), isClickable()),
)).to.be.rejectedWith(AssertionError, `Expected non-existent element to become present`));

/** @test {isClickable} */
it('does not exist in a list of PageElements', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistentElements().first(), isClickable()),
)).to.be.rejectedWith(AssertionError, `Expected the first of non-existent elements to become present`));
});

/** @test {isClickable} */
Expand Down
62 changes: 44 additions & 18 deletions integration/web-specs/spec/expectations/isEnabled.spec.ts
Expand Up @@ -3,43 +3,69 @@ import 'mocha';
import { expect } from '@integration/testing-tools';
import { Ensure } from '@serenity-js/assertions';
import { actorCalled, AssertionError, Wait } from '@serenity-js/core';
import { By, isEnabled, Navigate, PageElement } from '@serenity-js/web';
import { By, isEnabled, Navigate, PageElement, PageElements } from '@serenity-js/web';

describe('isEnabled', function () {

const Page = {
enabledButton: PageElement.located(By.id('enabled')).describedAs('the enabled button'),
disabledButton: PageElement.located(By.id('disabled')).describedAs('the disabled button'),
hiddenButton: PageElement.located(By.id('hidden')).describedAs('the hidden button'),
const Elements = {
nonExistent: () => PageElement.located(By.id('does-not-exist')).describedAs('non-existent element'),
nonExistentElements: () => PageElements.located(By.id('does-not-exist')).describedAs('non-existent elements'),
enabledButton: () => PageElement.located(By.id('enabled')).describedAs('the enabled button'),
disabledButton: () => PageElement.located(By.id('disabled')).describedAs('the disabled button'),
hiddenButton: () => PageElement.located(By.id('hidden')).describedAs('the hidden button'),
};

beforeEach(() =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/expectations/is-enabled/buttons.html'),
));

/** @test {isEnabled} */
it('allows the actor flow to continue when the element is enabled', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Page.enabledButton, isEnabled()),
Ensure.that(Page.enabledButton, isEnabled()),
)).to.be.fulfilled);
describe('resolves to true when the element', () => {

/** @test {isEnabled} */
it('breaks the actor flow when element is disabled', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Page.disabledButton, isEnabled()),
)).to.be.rejectedWith(AssertionError, `Expected the disabled button to become enabled`));
/** @test {isEnabled} */
it('is enabled', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Elements.enabledButton(), isEnabled()),
Ensure.that(Elements.enabledButton(), isEnabled()),
)).to.be.fulfilled);
});

describe('resolves to false when the element', () => {

/** @test {isEnabled} */
it('is disabled', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.disabledButton(), isEnabled()),
)).to.be.rejectedWith(AssertionError, `Expected the disabled button to become enabled`));

/** @test {isEnabled} */
it('is hidden', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.hiddenButton(), isEnabled()),
)).to.be.rejectedWith(AssertionError, `Expected the hidden button to become visible`));

/** @test {isActive} */
it('does not exist', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistent(), isEnabled()),
)).to.be.rejectedWith(AssertionError, `Expected non-existent element to become present`));

/** @test {isActive} */
it('does not exist in a list of PageElements', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistentElements().first(), isEnabled()),
)).to.be.rejectedWith(AssertionError, `Expected the first of non-existent elements to become present`));
});

/** @test {isEnabled} */
it('contributes to a human-readable description of an assertion', () => {
expect(Ensure.that(Page.enabledButton, isEnabled()).toString())
expect(Ensure.that(Elements.enabledButton(), isEnabled()).toString())
.to.equal(`#actor ensures that the enabled button does become enabled`);
});

/** @test {isEnabled} */
it('contributes to a human-readable description of a wait', () => {
expect(Wait.until(Page.enabledButton, isEnabled()).toString())
expect(Wait.until(Elements.enabledButton(), isEnabled()).toString())
.to.equal(`#actor waits up to 5s, polling every 500ms, until the enabled button does become enabled`);
});
});
66 changes: 43 additions & 23 deletions integration/web-specs/spec/expectations/isSelected.spec.ts
Expand Up @@ -3,49 +3,69 @@ import 'mocha';
import { expect } from '@integration/testing-tools';
import { Ensure } from '@serenity-js/assertions';
import { actorCalled, AssertionError, Wait } from '@serenity-js/core';
import { By, isSelected, Navigate, PageElement } from '@serenity-js/web';
import { By, isSelected, Navigate, PageElement, PageElements } from '@serenity-js/web';

describe('isSelected', function () {

const Languages = {
typeScript: PageElement.located(By.css('select[name="languages"] > option[value="TypeScript"]')).describedAs('the TypeScript option'),
javaScript: PageElement.located(By.css('select[name="languages"] > option[value="JavaScript"]')).describedAs('the JavaScript option'),
java: PageElement.located(By.css('select[name="languages"] > option[value="Java"]')).describedAs('the Java option'),
const Elements = {
nonExistent: () => PageElement.located(By.id('does-not-exist')).describedAs('non-existent element'),
nonExistentElements: () => PageElements.located(By.id('does-not-exist')).describedAs('non-existent elements'),
typeScript: () => PageElement.located(By.css('select[name="languages"] > option[value="TypeScript"]')).describedAs('the TypeScript option'),
javaScript: () => PageElement.located(By.css('select[name="languages"] > option[value="JavaScript"]')).describedAs('the JavaScript option'),
java: () => PageElement.located(By.css('select[name="languages"] > option[value="Java"]')).describedAs('the Java option'),
};

beforeEach(() =>
actorCalled('Wendy').attemptsTo(
Navigate.to('/expectations/is-selected/programming_languages.html'),
));

/** @test {isSelected} */
it('allows the actor flow to continue when the element is selected', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Languages.typeScript, isSelected()),
Ensure.that(Languages.typeScript, isSelected()),
)).to.be.fulfilled);
describe('resolves to true when the element', () => {

/** @test {isSelected} */
it('breaks the actor flow when element is not selected', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Languages.javaScript, isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected the JavaScript option to become selected`));
/** @test {isSelected} */
it('is selected', () =>
expect(actorCalled('Wendy').attemptsTo(
Wait.until(Elements.typeScript(), isSelected()),
Ensure.that(Elements.typeScript(), isSelected()),
)).to.be.fulfilled);
});

/** @test {isSelected} */
it('breaks the actor flow when element is not present', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Languages.java, isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected the Java option to become selected`));
describe('resolves to false when the element', () => {

/** @test {isSelected} */
it('is not selected', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.javaScript(), isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected the JavaScript option to become selected`));

/** @test {isSelected} */
it('is not present', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.java(), isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected the Java option to become selected`));

/** @test {isSelected} */
it('does not exist', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistent(), isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected non-existent element to become present`));

/** @test {isSelected} */
it('does not exist in a list of PageElements', () =>
expect(actorCalled('Wendy').attemptsTo(
Ensure.that(Elements.nonExistentElements().first(), isSelected()),
)).to.be.rejectedWith(AssertionError, `Expected the first of non-existent elements to become present`));
});

/** @test {isSelected} */
it('contributes to a human-readable description of an assertion', () => {
expect(Ensure.that(Languages.typeScript, isSelected()).toString())
expect(Ensure.that(Elements.typeScript(), isSelected()).toString())
.to.equal(`#actor ensures that the TypeScript option does become selected`);
});

/** @test {isSelected} */
it('contributes to a human-readable description of a wait', () => {
expect(Wait.until(Languages.typeScript, isSelected()).toString())
expect(Wait.until(Elements.typeScript(), isSelected()).toString())
.to.equal(`#actor waits up to 5s, polling every 500ms, until the TypeScript option does become selected`);
});
});

0 comments on commit de4610c

Please sign in to comment.