It's best practice to use "page objects" with WebDriver. See this video if you're not familiar with them.

Make pages directory.

Make pages/rsvp.js file.

const{Browser,By,Key,until}=require("selenium-webdriver");consturl="https://treehouse-projects.github.io/selenium-webdriver-intermediate/waits/app/index.html";// Define a class to represent the page.// Instances of this class will be responsible for controlling WebDriver to do operations on the page.classRsvpPage{// The class constructor sets up new instances of the class.// We're going to have the test pass in a browser driver object when creating a page object.// The page object will call methods on the driver object to control the page.constructor(driver){// We'll set the "driver" property of the new object to equal the driver that's passed in.this.driver=driver;// The locators property will be an object containing locators for the different page// elements we need to find.this.locators={// Our first test uses this locator to find the list that we'll populate with invitees.// We'll move the locator from the test to here.invitedList:By.id('invitedList'),// Our second tests uses this locator to find the form to register invitees.// We'll move that here, too.registrationForm:By.id('registrar'),}}// This method will load the page. It will use whatever driver object we pass into the constructor.open(){this.driver.get(url);}}// We need to set the values that will be returned when this module is required from another module.// We'll set it up to return the RsvpPage class.module.exports=RsvpPage;

Then we need to update our test to load our new module and use the class.

const{Browser,By,Key,until}=require("selenium-webdriver");const{suite}=require("selenium-webdriver/testing");constassert=require('assert');// Load the RsvpPage class.constRsvpPage=require('../pages/rsvp.js')suite(function(env){describe('RSVP site',asyncfunction(){letdriver;// Define a variable to hold the page object here so that it stays in scope// for all our tests as well.letpage;before(asyncfunction(){driver=awaitenv.builder().build();// Create a new page object that will use our driver object.// Store it in the page variable.page=newRsvpPage(driver);// Instead of calling driver.get() ourselves, we'll let the page object// load the page for us.awaitpage.open();});it('has invitee list',asyncfunction(){// Use the locator from the page object instead.letelements=awaitdriver.findElements(page.locators.invitedList);assert(elements.length>0);});it('has registration form',asyncfunction(){// Use the locator from the page object instead.letelements=awaitdriver.findElements(page.locators.registrationForm);assert(elements.length>0);});after(asyncfunction(){driver.quit();});});});

0:00

We've made a lot of improvements
to our test code, but

0:02

there's one more change we need to make
to bring it in line with best practices.

0:06

In Selenium, it's generally best to wrap
up interactions with the actual page in

0:11

a page object class.

0:12

If you're not familiar with Selenium
page object, see the teacher's notes.

0:17

Let's create a new directory
that'll hold our page object code.

0:20

So within our project directory,

0:22

I'm gonna create a new subdirectory
to hold all our page object code.

0:26

I'll name it Pages,
within the Pages subdirectory,

0:30

I'll create a new file to
hold our RSVP page object.

0:36

And I'll name it rsvp.js.

0:40

First up we're going to need to import
some of the code from Selenium web driver

0:43

into this file as well.

0:44

So I'm gonna copy this over
from the actual test code.

0:52

And part of the whole point for creating
a page object class is to ensure that our

0:56

test itself doesn't have to know
the details of how to connect to the page.

0:59

So I'm going to move this URL for
the page out of our test and

1:04

over to the page object class file.

1:08

Now I need to define a class
to represent the page.

1:10

Instances of this class
will be responsible for

1:13

controlling web driver to
do operations on the page.

1:16

So, I'll say class RsvpPage,

1:23

we need to set the values that we will be
returned when this module is required from

1:27

another module.

1:28

We'll set it up to return
the RsvpPage class.

1:31

So I'll say module.exports=RsvpPage.

1:39

The class constructor sets up
new instances of the class.

1:43

We're going to have the test pass in
a browser driver object when creating

1:47

a page object.

1:48

The page object will call methods on
the driver object to control the page.

1:53

We'll set the driver property of the new
object to equal the driver that's

1:56

passed in.

2:01

The locators property will be
an object containing locators for

2:04

the different page
elements we need to find.

2:09

Our first test uses a locator to find
the list that will populate with invitees.

2:14

We'll move the locator
from the test to here.

2:17

So I'll set the invitedList
attribute of the locators object

2:22

to equal By.id, and we'll look for
an ID of invitedList.

2:30

Our second test uses a locator to
find the form to register invitees,

2:34

we'll move that here, too.

2:36

We'll set up a property
named registationForm.

2:41

And we'll use the By.id locator
with an ID of registrar.

2:49

We're also going to want
a method to load the page.

2:52

It'll use whatever driver object
we pass into the constructor.

2:55

So we'll name our method open.

3:00

And within the open method,

3:02

we'll use the driver object that
belongs to this page object.

3:08

And we'll call the get method on it to
get the URL constant that we set above.

3:14

Let's save that, and now we need to update
our test to load our new module and

3:18

use the class.

3:20

So up here, I'll load the RsvpPage
class from our new module.

3:32

The double dot here means to
go to the parent directory,

3:35

the parent of the test directory.

3:38

And then go to the pages subdirectory,
and load the rsvp.js module.

3:45

Next, just as we did for our driver
variable, we need to define the variable

3:49

up here to hold the page object, so
that it stays in scope for all our tests.

3:53

So, we'll say let's page,

3:56

we won't actually assign anything to the
page variable until our before function.

4:01

Where we'll say that
the page = new RsvpPage

4:07

object and we'll pass the driver to
the constructor for that object.

4:14

And instead of calling
driver.get ourselves,

4:16

we'll let the page object
load the page for us.

4:18

So we'll say page.open Now remember,
we're trying to move all knowledge

4:24

of how to interact with the page, from our
test itself into the page object class.

4:29

So we're gonna take this snippet
of code here, that looks for

4:33

an element with an id of invitedList.

4:36

And instead,

4:37

we're going to use the locator that we
set up here in our page object class.

4:41

So we'll say page.locaters.invitedList.

4:49

We'll do the same down here
instead of looking an element up

4:52

with an id of registrar.

4:54

We'll just say that we're going
to use the locater that is

4:59

defined in our page object class,
which is registrationForm.

5:06

Let's make sure that our test is saved and
make sure that our page objects is

5:12

saved as well, and go back to our
terminal, and try running mocha again.