"A procedure for critical evaluation; a means of determining the presence, quality, or truth of something; a trial." - Wordnik definition
The Practical Test Pyramid - Martin Fowler
Succeeding with Agile - Mike Cohn
Yes!!!
🤘🤙💪🤌🍻
Given:
When:
Then:
I am logged in as a Content Editor
I navigate to "/admin/configuration"
I should see an "Access Denied" message.
Arrange:
Act:
Assert:
Drupal is for ambitious site builders - 2022
Drupal is for ambitious digital experiences - 2017
Come for the code (software), stay for the community!
Drupal is for Mid-Market - 2012
$ drush gen
test:
test:browser (browser-test) Generates a browser based test
test:kernel (kernel-test) Generates a kernel based test
test:nightwatch (nightwatch-test) Generates a nightwatch test
test:unit (unit-test) Generates a unit test
test:webdriver (webdriver-test) Generates a test that supports JavaScript
"In the struggle between yourself
and the world, second the world."
- Franz Kafka
{
"require": {
"behat/behat": "~3.0",
"behat/mink": "~1.5",
"behat/mink-extension": "~2.0",
"behat/mink-goutte-driver": "~1.0",
"behat/mink-selenium2-driver": "~1.1",
"behat/mink-browserkit-driver": "~1.1",
"drupal/drupal-extension": "~3.0"
},
"config": {
"bin-dir": "bin/"
}
}
{
"require": {
"behat/mink-extension": "~2.3",
"behat/mink-goutte-driver": "~1.2",
"behat/mink-selenium2-driver": "~1.3"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"bin-dir": "bin/"
}
}
sauce:
sauce_labs:
# Browser defaults to Firefox if left out.
browser: "chrome"
#browser: "internet explorer"
capabilities:
# Need to add this fake data or else Sauce Labs fails.
# Problem in the behat/mink-extension project.
# Located in vendor/behat/mink-extension/src/Behat/MinkExtension/ServiceContainer/Driver/SauceLabsFactory.php
custom-data:
foo: "bar"
baz: "biz"
# OS to test against.
platform: "macOS 10.13"
#platform: "Windows 10"
# Version of browser to use.
#version: "63.0"
/**
* Wait for AJAX to finish.
*
* @Given I wait for AJAX
*/
public function iWaitForAjax() {
$this->getSession()->wait(2000,
'typeof jQuery !== "undefined"
&& jQuery.active === 0
&& document.readyState === "complete"');
}
/**
* @When /^I click the "(?P<element>(?:[^"]|\\")*)" element$/
*
* @param $element
*/
public function iClickTheElement($element) {
$page_element = $this->getSession()
->getPage()
->find("css", $element)
->click();
}
@layout
Scenario Outline: The layout settings form should
be available for certain roles.
Given I am logged in as a user with the <role> role
When I am on "admin/config/content/express-layout"
Then I should not see <message>
Examples:
| role | message |
| edit_only | "the message..."
# 2) TEST THAT A SIMPLE BLOCK CAN BE CREATED AND REVISED
Scenario: Block Functionality - A very simple Slider can be created
Given I am logged in as a user with the "site_owner" role
And I am on "block/add/slider"
And fill in "edit-label" with "Slider Label"
And fill in "edit-title" with "My Slider Title"
And I fill in "edit-field-slider-slide-und-0-field-slider-image-und-0-alt" with "Mountain Fantasy"
And I attach the file "behatBanner1.jpg" to "edit-field-slider-slide-und-0-field-slider-image-und-0-upload"
When I press "edit-submit"
Then I should be on "block/slider-label/view"
And I should see "My Slider Title"
And CU - I should...cry :(
"Cypress is a next generation front end testing tool built for the modern web. We address the key pain points developers and QA engineers face when testing modern applications."
describe('Authentication tests', () => {
it('logs in without custom command', function () {
cy.visit('/user/login');
cy.get('#edit-name').type('admin');
cy.get('#edit-pass').type('1234!');
cy.get('input[value="Log in"]').click();
cy.visit('/admin/content');
cy.get('h1.page-title')
.should('be.visible')
.contains('Content');
});
});
Cypress.Commands.add('login', (user, password) => {
return cy.session(user, () => {
cy.request({
method: 'POST',
url: '/user/login',
form: true,
body: {
name: user,
pass: password,
form_id: 'user_login_form'
}
});
},
{
cacheAcrossSpecs: true,
});
});
cy.login('admin', '1234!');
// Upload an image.
cy.get('#edit-field-image-0-upload')
.selectFile('cypress/images/beehat-drupalicon-small.png');
cy.get('input[name="field_image[0][alt]"]')
.type('Beehat Druplicon');
// Fill out the body field.
cy.get('div[aria-label="Editor editing area: main"]').click();
cy.realType('Bob{enter}McDougle{enter}');
cy.get('button[data-cke-tooltip-text="Bulleted List"]').realClick();
cy.realType('Milk{enter}Eggs{enter}Bread{enter}{enter}');
cy.get('button[data-cke-tooltip-text="Block quote"]').realClick();
cy.realType('It\'s today, not yesterday!');
// Add tags to the article.
cy.get('#edit-field-tags-target-id').type('foo,bar{enter}');
// Save the article node.
cy.get('#edit-submit').click();
<button
data-cy="submit"
id="the-button"
class="btn btn-large"
>
Submit
</button>
<script>
// Brittle.
cy.get('button.btn').click()
// Safer.
cy.get('[data-cy="submit"]').click()
</script>
// Confirm the article was created with the right title, body, image, and tags.
cy.get('.title').contains( 'New Article');
cy.get('img[alt="Beehat Druplicon"]').should('be.visible');
cy.get('.text-content.field--name-body').should('have.html',
`<p>Bob</p><p>McDougle</p><ul><li>Milk</li><li>Eggs</li><li>Bread</li>`
+ `</ul><blockquote><p>It's today, not yesterday!</p></blockquote>`);
cy.get('.field--name-field-tags a').eq(0).contains('foo');
cy.get('.field--name-field-tags a').eq(1).contains('bar');
expect({ name: 'Jane' }).to.deep.equal({ name: 'Jane' })
// Without an alias.
beforeEach(() => {
cy.get('button').then(($btn) => {
const text = $btn.text()
})
})
it('does not have access to text', () => {
// how do we get access to text ?!?!
})
// With an alias.
beforeEach(() => {
// alias the $btn.text() as 'text'
cy.get('button').invoke('text').as('text')
})
it('has access to text', function () {
this.text // is now available
cy.get('@text').should('equal', 'The Button')
})
// Stub two requests and don't assert until loaded...
cy.intercept('GET', '/api/users', {fixture: 'users-list.json'}).as('getUsers')
cy.intercept('GET', '/api/users/*').as('getUsersData')
cy.visit('/users-list-page');
cy.wait(['@getUsers', '@getUsersData']);
cy.get('ul[data-testid="users-list"] li').first().should('have.text', 'John Doe')
// Add a language header to a response.
cy.intercept('/es/*', (req) => {
req.on('before:response', (res) => {
res.headers['x-language-origin'] = 'es-us'
})
})
// Track GET and POST of the media browser URL.
cy.intercept(
'/media/browser?render=media-popup&id=media_wysiwyg&plugins='
).as('mediaBrowserURL')
cy.wait('@mediaBrowserURL').get('iframe#mediaBrowser').iframe()
.then((iframes) => {})
Feature
Bug
/regressions
/editor
/content_admin
/e2e
...
it.skip('NJDC-1234: Broken')
it.skip('will implement next')
Feature
Bug
/regressions
/editor
/content_admin
/e2e
Integration Tests
// @see https://jira.hell/NJDRC-1234 for more details.
it('can correctly preview a node', () => {
cy.login('administrator');
cy.visit('/node/add/page');
cy.get('#edit-title-0-value')
.type('Test page for preview');
cy.get('#edit-grpselections')
.select(1);
cy.get('#edit-preview')
.click();
cy.get('.node-preview-container')
.should('be.visible');
});
describe('Verify some CSS and stuff...', () => {
before(() => {
cy.visit('/the-page');
})
it('Verify heading has correct CSS and title', () => {
cy.get('h1')
.should('have.css', 'font-family', '"Open Sans", helvetica, sans-serif')
.should('contain', 'The title I want to see')
cy.visit('/about');
})
it('Verify the special field does not exist', () => {
cy.get('h1 > span.field--name-special').should('not.exist')
})
it('Verify the trademark has correct CSS', () => {
cy.get('.field__item span.trademark-entity')
.should('have.css', 'font-family', '"Open Sans", helvetica, sans-serif')
.should('contain', '™')
})
})
describe('Tests and stuff', () => {
it('Verify some CSS and stuff...', () => {
cy.visit('/the-page');
cy.get('h1')
.should('have.css', 'font-family', '"Open Sans", helvetica, sans-serif')
.should('contain', 'The title I want to see')
cy.visit('/about');
cy.get('h1 > span.field--name-special').should('not.exist')
cy.get('.field__item span.trademark-entity')
.should('have.css', 'font-family', '"Open Sans", helvetica, sans-serif')
.should('contain', '™')
})
})
describe('FAQs tests', function () {
it('should load the FAQs list and navigate to answer sections', function () {})
it('should load individual FAQ pages', function () {})
})
describe('Credit card Donation-related tests', function() {
it('Makes a single, plain donation via credit card' +
'and tests default form validation', function () {})
})
describe("Giving Form Query Parameters Modifications", () => {
it('defaults without query parameters', function() {})
it('suggested amount with the fund\'s suggested_amount field', function() {})
it('suggested amount with `amount` from `defaultGivingOptions`', function() {})
it('suggested amount with `amount` from `other-amount`', function() {})
it('show recurring schedule with annually', function() {})
})
describe('Authentication tests - using a matrix', () => {
const testUsers = [
{name: 'da_boss', one: 'Content', two: 'Basic site settings'},
{name: 'editor', one: 'Content', two: 'Access denied'},
{name: 'a-failure', one: 'Access denied', two: 'Access denied'}
];
testUsers.forEach(user => {
it(`Logs in as ${user.name}`, () => {
// Log in as the user.
cy.login(user.name, user.name);
// Go visit a page that requires authentication.
cy.visit('/admin/content', {failOnStatusCode: false});
// Confirm the user sees the right message.
cy.get('h1.page-title').contains(user.one);
// Go visit a page that requires more authentication.
cy.visit('/admin/config/system/site-information', {failOnStatusCode: false});
// Confirm the user sees the right message.
cy.get('h1.page-title').contains(user.two);
})
})
})
class UserAdminTest extends BrowserTestBase {
public function testRateManagerAccess() {
// Anonymous users should have no access at all.
$this->drupalGet('/admin/ratemanager');
$this->assertSession()->statusCodeEquals(403);
// Create a user with role.
$low_user = $this->createUser();
$low_user->addRole('editor_offer_only');
$low_user->save();
// Log in a privileged user with no supplier.
$this->drupalLogin($low_user);
$this->drupalGet('/admin/ratemanager');
$this->assertSession()->statusCodeEquals(200);
}
Cypress Downsides: