Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
This example covers the generation of test data and oracles and the combination of features using states.
Let's suppose that we have to create two simple features for a web application: "Login" and "Add Product". After discussing them with stakeholders and the team, we created the following Concordia specification (see the two tabs):
Feature: Login
As a user
import "login.feature"
Feature: Add Product
As an employee
I would like to add a product
In order to manage its data and sale it
Scenario: Add a new product with basic data
Given that I am logged in
When I add a product with SKU, description, department, price, and quantity
Then I am able to register it
Variant: Basic data
Given that I have a ~user logged in~
and I visit the [Product Screen]
When I fill {SKU}
and I fill {Description}
and I fill {Price}
and I fill {Quantity}
and I click on {Save}
Then I have ~product registered~
and I see "Saved."
UI Element: SKU
- required
Otherwise I see "Please inform the SKU."
- format is "/[A-Z]{3}\-[0-9]{3}/"
Otherwise I see "Invalid SKU format. Please use 3 letters, dash, 3 numbers."
UI Element: Description
- minimum length is 2
Otherwise I see "Description needs at least 2 characters."
- maximum length is 100
Otherwise I see "Description must have at most 100 characters."
UI Element: Price
- required
Otherwise I see "Please inform the price."
- minimum value is 0.01
Otherwise I see "Minimum price is one cent."
UI Element: Quantity
- data type is integer
UI Element: Save
- type is button
Constants:
"Product Screen" is "/products/new"
These features could correspond to the following sketches:
The specification helps stakeholders, analysis, testers and developers to create a shared understanding about the application and its business rules. It helps with the requirements validation, knowledge documentation, project design, implementation and testing.
Different from Use Cases and other requirements specification formats, with Concordia you probably don't need to define different scenarios for validation. Instead of having to manually define a set scenarios for validation, you just need to define how the UI elements should behavior - in their declaration - and the compiler will generate different scenarios for you in the form of Test Cases. Whether your application has a lot of such rules, your team will certainly benefit from it.
<TO-DO (UNDER CONSTRUCTION)>
that a Variant express a possible interaction between a user and the application in order to complete its (business-focused) Scenario. lt follows the syntax and always uses the word "I" to represent the user or user role denoted in the Feature's description.
Let's run Concordia Compiler with a to produce the same results over and over again:
It will generate login.testcase with the following content:
If a seed is not given, Concordia Compiler assumes the current date and time. The seed is always printed in the console so that you or your team can reproduce the same paths, test data, and test oracle that were able to expose a bug in your application. Here we are giving it for a sake of reproduction.
Using the same seed over and over again will make Concordia Compiler produce the same results. Options no-run and no-result avoid running the test scripts and getting execution results.
This will not generate test cases or test scripts, but it will execute them and get their results:
login.testcase:
test/login.js:
Whether you ran the test scripts above, you probably saw they fail. That's because they didn't find a web application running at http://localhost/login or because the application was found but it did not match the expected behavior. You may adapt your application and run the tests again.
Concordia shows a report that indicates the failures' locations. They help you to decide if a failure was caused by the application under test (e.g., it did not behave as expected) or because of the requirements specification (e.g., it is outdated in relation to the application).
Now keeping your specification updated has a new clear benefit: you can use it to generate tests and discover existing defects in your application!
Concordia to retrieve test data or to set the testing environment up before or after running test scripts.
Use --db-install plus the driver name. Example:
Available driver names:
Overview of the Concordia declarations:
A Featuredefines a desired behavior for the software, a piece of functionality that gives some business value. A Scenario is a high-level usage scenario of a Feature. A Feature can have many Scenarios.
Since Feature and Scenario are business-oriented declarations, they are not used by Concordia Compiler to generate test cases. You can write them freely. Although, we recommend you to use the template for a Feature
Welcome to the Concordia 2 documentation! This page will help you to get started fast. If you run into problems, you can find help on our .
Concordia Compiler requires only version 12.20 (or above) and works on Linux, MacOS and Windows.
In this Quick Start, we'll create a simple specification of an interaction with a search page and use . Therefore, download it before continuing.
If you are starting a new project or whether your project does not have a file named package.json, then run:
Install Concordia Compiler :
Concordia Compiler will ask you about some preferences. Press Enter
This page tries to identify in which version of the Compiler a certain language construction became available.
👁🗨 "Planned" and "Available" refer to versions of the Compiler.
This page presents Concordia actions and their corresponding commands for some frameworks.
Help us improving this page. Send us a message on or open an Issue at . 💖
👉 Under construction.
Concordia
Action [+target]
Appium
CodeceptJS
Cypress
Playwright
Puppeteer
TestCafé
amOn
amOnPage
goto
check
checkOption
check
clear + element
clearField
N/A
clear + cookie
clearCookie
clearCookies
click
click
doubleClick
doubleClick
dblclick
fill
fill
N/A
focus
move
moveTo
moveCursorTo
trigger
hover
hover
hover
press
press
refresh
refreshPage
reload
resize
resizeWindow
setViewportSize
rightClick
rightClick
saveScreenshot
saveScreenshot
screenshot
scrollTo
scrollTo
scrollTo
select
selectOption
selectOption
switch + element
switchTo
N/A
switch + next tab
switchToNextTab
N/A
switch + previous tab
switchToPreviousTab
N/A
tap
tap
N/A
type
type
wait + seconds
wait
N/A
wait + element
waitForElement
waitForElement
uncheck
uncheckOption
uncheck
Planned
Available
#
0.1
0.x
#language
0.1
0.x
#locale
2.1
Not released yet.
@*
0.1
0.x
@scenario
0.1
0.x
@variant
0.1
0.x
@ignore
0.1
0.x
@importance
0.1
0.x*
@generated
0.1
0.x
@fail
0.1
0.x
@global
0.1
Not released yet.
@extends
0.1
Not released yet.
@category
0.1
Not released yet.**
@issue
0.1
Not released yet.**
@generate-only-valid-values
1.0
1.1
Feature
0.1
0.x
Scenario
0.1
0.x
Background
0.1
Not released yet.**
Variant
0.1
0.x
Variant Background
0.1
Not released yet.
~state~
0.1
0.x
Constants
0.1
0.x
Table
0.1
0.x
Database
0.1
0.x
UI Element
0.1
0.x
UI Element property locale
1.4
2.0
UI Element property locale format
1.4
2.0
UI Element properties with date and time expressions
1.4
2.0
Test Case
0.1
0.x
Before All
0.1
0.x
After All
0.1
0.x
Before Feature
0.1
0.x
After Feature
0.1
0.x
Before Each Scenario
0.1
0.x
After Each Scenario
0.1
0.x
*=Not used yet. **=It may not be released, since it is not needed.
Construction
note
csv
CSV files
xlsx
Excel files
firebase
Firebase
ini
INI files
json
JSON files
Already installed
mysql
MySQL
adodb
MS Access
Windows only
mssql
MS SQL Server
postgres
PostgreSQL
sqlite
SQLite
Database drivers are installed as development dependencies of your application.
Use --db-uninstall plus the driver name. Example:
Just uninstall and then install it again.
A Database is global declaration. You must declare it once and then import its file in other features. Example:
A database can be referenced inside UI properties and Test Events by its name between [ and ]. Example: [My DB]. To reference a database table, separate the table name from the database name with with a dot (.) - e.g. [My DB.profession]. Let's see an example:
Instead of reinventing the wheel, Concordia uses SQL to query and modify databases and files. Even JSON and INI files can be queried with SQL.
In the example above, the property value from the UI Element "Profession" indicates that its (valid) values are retrieved from the table "profession" that belongs to the database declared as "My DB". The test event Before Each Scenario was declared to connect to the database, clean the table "profession", and then insert new records that the UI Element "Profession" will use.
Driver Name
Database
Database: My DB
- type is "mysql"
- host is "127.0.0.1"
- name is "acme"
- username is "tester"
- password is "secret"
Database: Another DB
- type is "json"
- path is "./db.json"import "db.feature"
# ... other declarations here ...
UI Element: Profession
- value comes from "SELECT name FROM [My DB.profession]"
Otherwise, I see "Profession not found."
- required
Otherwise, I see "Please select the profession."
Before Each Scenario:
Given that I connect to [My DB]
When I run the script 'DELETE FROM profession'
and I run the script 'INSERT INTO profession (name) VALUES ("Enginner"), ("Accountant")'
npx concordia --db-install mysqlnpx concordia --db-uninstall mysqlnpx concordia --seed="example" --no-run --no-result<TO-DO (UNDER CONSTRUCTION)><TO-DO (UNDER CONSTRUCTION)>$ concordia --plugin=codeceptjs --no-test-case --no-script# Generated with ❤ by Concordia
#
# THIS IS A GENERATED FILE - MODIFICATIONS CAN BE LOST !
import "login-en.feature"
@generated
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 1
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "*RM)O," # invalid: inexistent element
And i fill <password> with "" # valid: last element
And I click on <ok> # {OK}
Then I must see "Invalid username" # from <username>
@generated
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 2
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "bob" # valid: filled
And i fill <password> with -8655972838932479 # invalid: inexistent element
And I click on <ok> # {OK}
Then I must see "Invalid password" # from <password>
@generated
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 3
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "alice" # valid: random element
And i fill <password> with "4l1c3pass" # valid: random element
And I click on <ok> # {OK}
Then I see "Welcome"
@generated
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 4
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "alice" # valid: last element
And i fill <password> with "4l1c3pass" # valid: filled
And I click on <ok> # {OK}
Then I see "Welcome"
@generated
@fail
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 5
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "" # invalid: not filled
And i fill <password> with "" # invalid: not filled
And I click on <ok> # {OK}
Then I see "Welcome"
@generated
@scenario(1)
@variant(1)
Test case: Successful login with valid credentials - 6
Given that I am in the "http://localhost/login" # [Login Screen]
When i fill <username> with "bob" # valid: first element
And i fill <password> with 123456 # valid: first element
And I click on <ok> # {OK}
Then I see "Welcome"// Generated with ❤ by Concordia
// source: c:\code\tmp\concordia-test-pt\login-en.testcase
//
// THIS IS A GENERATED FILE - MODIFICATIONS CAN BE LOST !
Feature("Login");
Scenario("Successful login | Successful login with valid credentials - 1", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", "*RMO,"); // (11,5) invalid: inexistent element
I.fillField("password", ""); // (12,7) valid: last element
I.click("ok"); // (64,7) {OK}
I.see("Invalid username"); // (14,5) from <username>
});
Scenario("Successful login | Successful login with valid credentials - 2", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", "bob"); // (21,5) valid: filled
I.fillField("password", "-8655972838932479"); // (22,7) invalid: inexistent element
I.click("ok"); // (64,7) {OK}
I.see("Invalid password"); // (24,5) from <password>
});
Scenario("Successful login | Successful login with valid credentials - 3", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", "alice"); // (31,5) valid: random element
I.fillField("password", "4l1c3pass"); // (32,7) valid: random element
I.click("ok"); // (64,7) {OK}
I.see("Welcome"); // (65,5)
});
Scenario("Successful login | Successful login with valid credentials - 4", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", "alice"); // (41,5) valid: last element
I.fillField("password", "4l1c3pass"); // (42,7) valid: filled
I.click("ok"); // (64,7) {OK}
I.see("Welcome"); // (65,5)
});
Scenario("Successful login | Successful login with valid credentials - 5", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", ""); // (52,5) invalid: not filled
I.fillField("password", ""); // (53,7) invalid: not filled
I.click("ok"); // (64,7) {OK}
I.see("Welcome"); // (65,5)
});
Scenario("Successful login | Successful login with valid credentials - 6", (I) => {
I.amOnPage("http://localhost/login"); // (61,5) [Login Screen]
I.fillField("username", "bob"); // (62,5) valid: first element
I.fillField("password", "123456"); // (63,7) valid: first element
I.click("ok"); // (64,7) {OK}
I.see("Welcome"); // (65,5)
});seedScenario. Example:This is the User Story template:
As a <role>
I would like to <goal to perform>
In order to <received benefit> (or So that <received benefit>)
And this is the Given-When-Then (GWT) template:
Given <some context or precondition>
When <some action is carried out>
Then <a result or postcondition is observed>
In the GWT template, additional sentences of each kind are written in the next line and receive the prefix and.
Features and Scenarios must have a name, but their description are not required. Thus, you can write the above example like this (although we recommend against):
Feature files receive the extension .feature. Only one feature per file is allowed.
A Variant is a test-oriented declaration that describes a possible interaction between a user and the system in order to accomplish a Scenario. You can write more than one Variantfor a same Scenario .
A Variant must use the GWT template and first person singular ("I") in its sentences. Concordia Compiler uses Natural Language Processing (NLP) techniques to understand Variant sentences, aiming to transform them into test cases. Example:
Concordia Compiler understands the sentences from line 7 to11 as being variations of the same action, fill.
Of course, the vocabulary is limited. Although, you can extend it whether you need (by adding new synonyms to a specific dictionary file). Actions and common variations are documented and suggestions are always welcomed.
In the sentences above, the values were denoted between quotation marks, e.g. "bob@example.com". Numeric values are also accepted and do not need quotation marks. Example: 97.
Widgets (of the user interface) are written between < and >. Example: <#email>. Concordia adopts the well-known CSS query selectors to locate widgets, regardless the user interface (its plugins convert them to the format adopted by the testing framework). You can also use XPath (although some plug-ins may not accept it). Examples:
# (hashtag) to find a widget by its id. Example: #email
. (dot) to find a widget by its class, if applicable. Example: .colorful
@ (at) to find a widget by its name. Example: @pass
~ (tilde) to find a widget by its mobile name, if applicable. Example: ~pass
no prefixed characters, to find a widget by its type. Example: input
// (double slash) to find a widget by its XPath. Example: //table/tbody/tr[1]
A State is denoted by a text between tilde (~), such as ~a state~, and must be written inside Variant sentences only. Example:
A Variant can both produce or require states. A State is produced by a Then sentence, like in the prior example. A State is required by a Given or a When sentence. For instance:
By declaring a required State, you're establishing a dependency among the current Variant and the ones that can produce it. Since that State is often produced by a Variant from another Feature, you have to import the corresponding Feature file. Concordia Compiler will only search for states from imported Feature files.
When transforming Variants into Test Cases, Concordia Compiler will:
Remove any (Then) sentences with a produced State; and
Replace any (Given or When) sentences with a required State by sentences from the Variant that can produce it.
Whether there are different Variants that can produce a (same) required State, Concordia Compiler will be able to combine each of them with the current Variant, aiming to produce Test Cases that explore different execution paths. For more information, please read the section .
Let's say that we have a simple Login feature like this:
Feature: Login
Scenario: Sucessful login
Variant: Login with username and password
Given that I am on the [Login Screen]
When I enter with "bob" in {Username}
and I enter with "123456" in {Password}
and I click on {OK}
Then I see "Welcome"
and I have ~user logged in~
Constants:
- "Login Screen" is "/page"
UI Element: Username
UI Element: Password
UI Element: OK
- type is buttonNow let's suppose that a user has to be logged in to access its account details:
import "login.feature"
Feature: My Account
Scenario: See my account data
Variant: Show basic data by default
Given that I have ~user logged in~
and I am on the [Account Screen]
Then I see {User} with "bob"
and I see {Name} with "Robert Downey Jr"
Constants:
- "Account Screen" is "/account"
UI Element: UserConcordia Compiler will produce my-account.testcase with a Test Case like the following, that includes steps from both the features:
A Test Case represents a test case produced for a certain Variant . Concordia Compiler can generate many Test Case declarations from a same Variant, each one covering different states, input combinations and oracles. The generation considers the following declarations:
Variant
states
UI Element
Constants
Table
Database
The simplest Test Case is one whose sentences are exactly equal to the Variant's. That's the case when the Variant does not use other declarations and it explicits all the input test data to use.
For comparison, whether we simply remove with "Bob"from the sentenceWhen I fill <#name> with "Bob" , Concordia Compiler will generate a pseudo-random value to complete the sentence, and it will be like this: When I fill <#name> with "A~!39esljdns so2u1 *ns%".
Concordia Compiler uses a single seed to generate pseudo-random numbers in its algorithms and values. Everytime it runs, a new seed is produced. You can set the seed using the CLI parameter --seed. By setting the seed, the compiler will pick the same paths and produce the same values - which is desirable for reproducing the same behavior in the application under test, but not for discovering new defects.
A UI Element represents a user interface element and can denote its expected behavior through properties.
Although a Widget is easy to declare, it can have some shortcomings:
1) It can make a sentence hard to read, specially when it uses XPath or CSS locators.
2. It can be hard to maintain. When a Widget appears in more than one Scenario and its locator needs to change (e.g. you have changed it in the UI), you need to search and replace all its occurrences in the document. With a UI Element, you just need to change the property locator.
3. It does not offer resources for test generation other than its locator.
A UI Element offers properties that can describe the behavior related to it. These properties are used to infer the test data and test oracles to generate.
Example:
These are the UI Element properties:
id or locator ⇨ how to locate the widget
type ⇨ widget type, like textbox, button, etc.
editable ⇨ whether the UI Element is editable (or not)
data type ⇨ data type of an editable UI Element
required⇨ whether the UI Element is required (or not)
format⇨ data format, denoted by a regular expression
value⇨ defines how the value is produced
minimum value ⇨ defines how the minimum value is produced
maximum value ⇨ defines how the maximum value is produced
minimum length ⇨ defines how the minimum length is produced
maximum length ⇨ defines how the maximum length is produced
By default, declaring a UI Element without properties is the same as declaring it with:
id or locator equal to the UI Element name in camelCase (e.g., "Net Price" becomes "netPrice"). The adopted case is configurable.
type equal to textbox
data type equal to string
Another example:
Please see the language reference for more details. Let's proceed with the other declarations for now.
A Import declaration allows you to import declarations from another .featurefile. Example:
Imported declarations are Feature, Constants, Table, Database, and UI Element.
A Constants block defines one or more constants. Every Constant holds an immutable value that is accessible through the declared name. Example:
Constants are accessed by their name between [ and ]. Example:
Constants are global declarations and cannot have duplicate names. You can use them in UI Element properties and Variant sentences.
A Table declares values in rows and columns to use in UI Element properties. Example:
Note that SQL is used to select values from the declared table, which is denoted between [ and ] (like Constants). Table names are global and share the same namespace as Constant names.
A Database provides a way to indicate a connection to an existing database or file. Example:
SQL is used to select values from a table or view from the declared database, which is denoted between [ and ] . Its table or view names are separated by a dot (.). Database names are global and share the same namespace as Tables and Constants.
In order to set the execution environment up, testers can define events that can run database commands or command-line scripts. These events can occur before or after Scenarios or Features:
Before Each Scenario: executes before every test of each Scenario of the current Feature.
After Each Scenario: executes after every test of each Scenario of the current Feature.
Before Feature: executes once, before all the tests of the current Feature.
After Feature: executes once, after all the tests of the current Feature.
Before All: executes once, before all the tests of the application under test.
After All: executes once, after all the tests of the application under test.
Test Events are written like Variants, using Given-When-Then. Since they usually perform actions, it is very likely to useWhen in most sentences. Example:
Commands and database scripts are denoted between apostrophe (').
Feature: Sales Report
As a manager
I would like to generate a sales report
In order to track company sales
Scenario: Daily sales report
Given that I am authenticated as a manager
When I open the sales report
and I choose the option "Daily report"
Then I see a list with all the today's sales
and the total valueFeature: Sales Report
Scenario: Daily sales reportFeature: New User
Scenario: Successful registration
Variant: Register with a new account
Given that I am on "https://example.com/register"
When I fill <#name> with "Bob"
and I fill <#dateOfBirth> with "11/30/1990"
and I enter with "bob@example.com" in <#email>
and I inform "m123456" in <#pass>
and I type "m123456" in <#confirmation>
and I click <#ok>
Then I see "Welcome, Bob" Then I have a ~user logged in~Given that I have ~user logged in~import "my-account.feature"
@scenario(1)
@variant(1)
Test Case: See my account data - 1
Given that I am on the "/login"
When I enter with "bob" in <username>
and I enter with "123456" in <password>
and I click on <ok>
Then I see "Welcome"
Given that I am on the "/account
Then I see <user> with "bob"
and I see <name> with "Robert Downey Jr"UI Element: Net Price
- minimum value is 10.00
Otherwise I see "Net Price must be greater than U$ 10" UI Element: Profession
- value is in [ "Programmer", "Tester", "Analyst" ]Import "login.feature"Constants:
- "Register Page" is "https://example.com/register"
- "Coupon Number" is 12345 Given that I am on the [Register Page]
When I fill {Coupon} with [Coupon Number]Table: Customer
| name | age |
| Ada Lovelace | 36 |
| Dennis Ritchie | 70 |
| Donald Knuth | 82 |
| George Boole | 49 |
| Niklaus Wirth | 83 |
UI Element: Name
- value comes from "SELECT name FROM [Customer]"
Otherwise I see "Customer not found."Database: AcmeDB
- type is "mysql"
- name is "acme"
- host is "http://127.0.0.1"
- username is "tester"
- password is "testing123"
UI Element: Name
- value comes from "SELECT name FROM [AcmeDB.customer]"
Otherwise I see "Customer not found."Before Feature:
Given that I connect to [AcmeDB]
When I run the script 'DELETE FROM customer'
and I run the script 'INSERT INTO customer ( name, age ) VALUES ( "Alice", 20 ), ( "Bob", 30 )'
and I run the command 'rmdir log'