serenity-js best practices

Created on 21 Nov 2016  ยท  3Comments  ยท  Source: serenity-js/serenity-js

Hi,

What is the best practices for serenity-js?
especially for the directory structure

from serenity-js-getting-started it have page object in ui
but the feature and test file all in one directory

what is the best practices for this?

thx

documentation

Most helpful comment

Hi @tremkonzah and apologies for being slow to reply on this.

As per our email conversation, "acceptance testing best practices" is a huge topic.
I started answering some of your questions in the "Serenity/JS Handbook" that's now available at serenity-js.org

I made a recommendation for some basic structure in the updated version of the "From Scripts to Serenity" tutorial.

Slice by type

Assuming you keep the test code under spec, and your application uses some imaginary "date picker" UI widget, you could organise your codebase _by type_.

This is a very simple structure that puts the ui components together in one directory, the tasks in another one and so on. This conceptually resembles the design of many MVC frameworks, where you'd have all the controllers in one place, all the models in another.

โ””โ”€โ”€ screenplay
    โ”œโ”€โ”€ components
    โ”‚   โ”œโ”€โ”€ date_picker.ts  <- contains targets and questions
    โ”‚   โ””โ”€โ”€ ...             <- other ui components, like "login_form.ts", "basket.ts" and so on
    โ””โ”€โ”€ tasks
        โ”œโ”€โ”€ open_date_picker.ts
        โ”œโ”€โ”€ close_date_picker.ts
        โ”œโ”€โ”€ pick_a_date.ts
        โ””โ”€โ”€ ...             <- other tasks related to other widgets, like "add_a_product.ts"
  • ๐Ÿ‘ The advantage of this structure is that it's very easy to start with, especially when you've just started working on a test suite and don't want to commit yourself to any particular design just yet. Options have value.
  • ๐Ÿ‘Ž The disadvantage is that if your application domain is complex, this slicing might not be sufficient for long, and you might end up with loads of files under tasks. This could negatively affect discoverability of your code.

Having said that, you can always start with this structure and refactor to a different one when changing it adds value.

One of the many benefits of using TypeScript is that it'll tell you straight away if by moving files around you've broken anything. It just won't compile :-)

Slice by component

Once the range and variation of the UI components you're working with starts to stabilise, you can try to slice the codebase _by component_.

If we take our imaginary date picker example again, we could propose the following structure:

โ””โ”€โ”€ screenplay
    โ”œโ”€โ”€ components
    โ”‚   โ”œโ”€โ”€ date_picker
    โ”‚   โ”‚   โ”œโ”€โ”€ components
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ date_picker.ts  <- contains targets and questions
    โ”‚   โ”‚   โ”œโ”€โ”€ index.ts            <- exposes the tasks to test scenarios
    โ”‚   โ”‚   โ””โ”€โ”€ tasks
    โ”‚   โ”‚       โ”œโ”€โ”€ open_date_picker.ts
    โ”‚   โ”‚       โ”œโ”€โ”€ close_date_picker.ts
    โ”‚   โ”‚       โ””โ”€โ”€ pick_a_date.ts
    โ”‚   โ”‚ 
    โ”‚   โ””โ”€โ”€ ...    <- other ui components, like "login_form.ts", "basket.ts" and so on
    โ””โ”€โ”€ tasks
        โ””โ”€โ”€ ...    <- other tasks related to other widgets, like "add_a_product.ts"
  • ๐Ÿ‘ The advantage is that it improves the discoverability of your code - it's obvious what tasks are associated with the date picker here
  • ๐Ÿ‘ It provides additional encapsulation, the index.ts only exposes the tasks, not the definition of the component structure (targets and questions)
  • ๐Ÿ‘ It can co-exist in the directory structure described in the previous example, when you slice by type, so you can refactor to this design later.
  • ๐Ÿ‘Ž This only works with well-defined UI widgets, that have enough functionality to justify the overhead of the directory structure (creating directories for each drop down of a search filter is an overkill, creating a directory for the _search component_ makes more sense)
  • ๐Ÿ‘Ž It's an overkill to start with. As with any design pattern, it works best if you think of it as a goal you might reach, rather than a starting point.

There are several other ways to do it though, and I just realised I should write a post about it ;-)

To summarise, start with slicing by type, and extract components when you learn more about them.

Hope this helps!
Jan

All 3 comments

Hi @tremkonzah and apologies for being slow to reply on this.

As per our email conversation, "acceptance testing best practices" is a huge topic.
I started answering some of your questions in the "Serenity/JS Handbook" that's now available at serenity-js.org

I made a recommendation for some basic structure in the updated version of the "From Scripts to Serenity" tutorial.

Slice by type

Assuming you keep the test code under spec, and your application uses some imaginary "date picker" UI widget, you could organise your codebase _by type_.

This is a very simple structure that puts the ui components together in one directory, the tasks in another one and so on. This conceptually resembles the design of many MVC frameworks, where you'd have all the controllers in one place, all the models in another.

โ””โ”€โ”€ screenplay
    โ”œโ”€โ”€ components
    โ”‚   โ”œโ”€โ”€ date_picker.ts  <- contains targets and questions
    โ”‚   โ””โ”€โ”€ ...             <- other ui components, like "login_form.ts", "basket.ts" and so on
    โ””โ”€โ”€ tasks
        โ”œโ”€โ”€ open_date_picker.ts
        โ”œโ”€โ”€ close_date_picker.ts
        โ”œโ”€โ”€ pick_a_date.ts
        โ””โ”€โ”€ ...             <- other tasks related to other widgets, like "add_a_product.ts"
  • ๐Ÿ‘ The advantage of this structure is that it's very easy to start with, especially when you've just started working on a test suite and don't want to commit yourself to any particular design just yet. Options have value.
  • ๐Ÿ‘Ž The disadvantage is that if your application domain is complex, this slicing might not be sufficient for long, and you might end up with loads of files under tasks. This could negatively affect discoverability of your code.

Having said that, you can always start with this structure and refactor to a different one when changing it adds value.

One of the many benefits of using TypeScript is that it'll tell you straight away if by moving files around you've broken anything. It just won't compile :-)

Slice by component

Once the range and variation of the UI components you're working with starts to stabilise, you can try to slice the codebase _by component_.

If we take our imaginary date picker example again, we could propose the following structure:

โ””โ”€โ”€ screenplay
    โ”œโ”€โ”€ components
    โ”‚   โ”œโ”€โ”€ date_picker
    โ”‚   โ”‚   โ”œโ”€โ”€ components
    โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ date_picker.ts  <- contains targets and questions
    โ”‚   โ”‚   โ”œโ”€โ”€ index.ts            <- exposes the tasks to test scenarios
    โ”‚   โ”‚   โ””โ”€โ”€ tasks
    โ”‚   โ”‚       โ”œโ”€โ”€ open_date_picker.ts
    โ”‚   โ”‚       โ”œโ”€โ”€ close_date_picker.ts
    โ”‚   โ”‚       โ””โ”€โ”€ pick_a_date.ts
    โ”‚   โ”‚ 
    โ”‚   โ””โ”€โ”€ ...    <- other ui components, like "login_form.ts", "basket.ts" and so on
    โ””โ”€โ”€ tasks
        โ””โ”€โ”€ ...    <- other tasks related to other widgets, like "add_a_product.ts"
  • ๐Ÿ‘ The advantage is that it improves the discoverability of your code - it's obvious what tasks are associated with the date picker here
  • ๐Ÿ‘ It provides additional encapsulation, the index.ts only exposes the tasks, not the definition of the component structure (targets and questions)
  • ๐Ÿ‘ It can co-exist in the directory structure described in the previous example, when you slice by type, so you can refactor to this design later.
  • ๐Ÿ‘Ž This only works with well-defined UI widgets, that have enough functionality to justify the overhead of the directory structure (creating directories for each drop down of a search filter is an overkill, creating a directory for the _search component_ makes more sense)
  • ๐Ÿ‘Ž It's an overkill to start with. As with any design pattern, it works best if you think of it as a goal you might reach, rather than a starting point.

There are several other ways to do it though, and I just realised I should write a post about it ;-)

To summarise, start with slicing by type, and extract components when you learn more about them.

Hope this helps!
Jan

My current approach to organising the files relies on continuous evolution and refactoring and could be summarised like this:

  • I start with one file, say screenplay.ts where I put all the tasks, interactions, questions and page objects. This helps me to get started with running the scenarios quickly without worrying too much about the structure at this stage:
โ””โ”€โ”€ screenplay.ts
  • As soon as the file becomes "too heavy" (it defines more than say 15-20 _"things"_) I split it up - in terms of timing, this might happen within a couple of hours from the point in time when I created the original screenplay.ts, so that file is rather short-lived. Also, I use the Task.where(), Interaction.where(), Question.about() syntax to keep the definitions short and compact.
  • When it comes to UI-based test automation, I prefer to slice by component, so as soon as I have a couple of test scenarios working with my original screenplay.ts and a couple of logical components identified, I create files for those components where I move tasks and page objects that deal only with those specific components.
  • At that point my directory structure might look like this:
โ”œโ”€โ”€ basket.ts       <- task, questions, interactions and page objects for the basket widget
โ”œโ”€โ”€ date_picker.ts  <- task, questions, interactions and page objects for date picker widget
โ””โ”€โ”€ screenplay.ts
  • As my understanding of each individual component evolves, and I add more page objects, tasks, interactions and questions, I split the component file and turn it into a directory to make the contents easier to find. If at this stage I have identified additional sub-components, I create the "component files" for those:
โ”œโ”€โ”€ basket                   <- sub-components of the basket component
โ”‚   โ”œโ”€โ”€ saved_for_later.ts
โ”‚   โ”œโ”€โ”€ summary.ts
โ”‚   โ”œโ”€โ”€ recently_viewed.ts
โ”‚   โ””โ”€โ”€ screenplay.ts        <- anything basket-related that doesn't fit within any of the sub-components
โ”œโ”€โ”€ date_picker.ts
โ””โ”€โ”€ screenplay.ts
  • At some point my screenplay.ts files will only contain tasks as questions, interactions and page objects will be placed together with their respective components. If that's the case I rename screenplay.ts to tasks.ts as it only has one responsibility of defining the tasks overarching several components.
โ”œโ”€โ”€ basket
โ”‚   โ”œโ”€โ”€ saved_for_later.ts
โ”‚   โ”œโ”€โ”€ summary.ts
โ”‚   โ”œโ”€โ”€ recently_viewed.ts
โ”‚   โ””โ”€โ”€ tasks.ts        <- tasks overarching several sub-components
โ”œโ”€โ”€ date_picker.ts
โ””โ”€โ”€ tasks.ts          <- tasks overarching several components
  • If it benefits the readability and discoverability of the design, or if I consider the tasks.ts files to be too large - I split them further and create a file per task:
โ”œโ”€โ”€ basket
โ”‚   โ”œโ”€โ”€ saved_for_later.ts
โ”‚   โ”œโ”€โ”€ summary.ts
โ”‚   โ”œโ”€โ”€ recently_viewed.ts
โ”‚   โ””โ”€โ”€ tasks             <- tasks overarching several sub-components
โ”‚       โ”œโ”€โ”€ AddProduct.ts
โ”‚       โ”œโ”€โ”€ ChangeQuantity.ts
โ”‚       โ”œโ”€โ”€ RemoveProduct.ts
โ”‚       โ””โ”€โ”€ ...
โ”œโ”€โ”€ date_picker.ts
โ””โ”€โ”€ tasks.ts
  • Of course, I can take that further. I might decide to separate page objects, tasks and interactions, for example:
โ”œโ”€โ”€ basket
โ”‚   โ”œโ”€โ”€ saved_for_later   <- page objects, interactions, questions live in separate "packages"
โ”‚   โ”‚   โ”œโ”€โ”€ ui.ts
โ”‚   โ”‚   โ”œโ”€โ”€ interactions.ts
โ”‚   โ”‚   โ”œโ”€โ”€ questions.ts
โ”‚   โ”‚   โ””โ”€โ”€ tasks.ts
โ”‚   โ”œโ”€โ”€ summary.ts
โ”‚   โ”œโ”€โ”€ recently_viewed.ts
โ”‚   โ””โ”€โ”€ tasks            
โ”‚       โ”œโ”€โ”€ AddProduct.ts
โ”‚       โ”œโ”€โ”€ ChangeQuantity.ts
โ”‚       โ”œโ”€โ”€ RemoveProduct.ts
โ”‚       โ””โ”€โ”€ ...
โ”œโ”€โ”€ date_picker.ts
โ””โ”€โ”€ tasks.ts

As you can see, this is an evolutionary process where the directory structure evolves together with my growing understanding of the product.

Does that help?

Jan

Was this page helpful?
0 / 5 - 0 ratings

Related issues

codesandtags picture codesandtags  ยท  4Comments

marktigno picture marktigno  ยท  3Comments

rajeshwarp picture rajeshwarp  ยท  5Comments

GeeChao picture GeeChao  ยท  6Comments

y3rsh picture y3rsh  ยท  4Comments