@bahmutov
Questions via Sli.do #cygofundme
Recording at https://on.cypress.io/webinars
/in/twilliams15
GoFundMe Charity is a subscription-free fundraising platform with powerful tools to make fundraising easy for charities
5-hour overnight test runs (~1,000 tests)
Random 10% (~100) tests fail from flake
Brittle code
Poor coding/QA practices
Difficult to debug & update
Questions via Sli.do #cygofundme
https://www.atlassian.com/inside-atlassian/qa
While devs prepare for a new feature,
QA helps set up the tests.
Then we just fill in the blanks.
No problem, QA has you covered
QA helps create a habit by writing unfinished tests.
Red, Green, Refactor
Selenium isn't very fun
Questions via Sli.do #cygofundme
npm install cypress
describe('Sign up', () => {
it('Users can create a new account')
})
describe('Sign in', () => {
it('Users can log in as a charity')
it('Users can log in as a human')
})
Example
beforeEach(() => {
cy.visit('/signin/form')
})
describe('Sign up', () => {
it('Users can create a new account')
})
describe('Sign in', () => {
it('Users can log in as a charity')
it('Users can log in as a human')
})
beforeEach(() => {
cy.visit('/signin/form')
})
describe('Sign up', () => {
beforeEach(() => {
cy.contains('Sign up')
.click()
})
it('Users can create a new account')
})
describe('Sign in', () => {
it('Users can log in as a charity')
it('Users can log in as a human')
})
beforeEach(() => {
cy.visit('/signin/form')
})
describe('Sign up', () => {
beforeEach(() => {
cy.contains('Sign up')
.click()
})
it('Users can create a new account')
})
describe('Sign in', () => {
describe('Charity', () => {
let charityUser
before(() => {
cy.charitySetup()
.then(response => {
charityUser = response.body
})
})
it('Users can log in as a charity')
})
describe('Human', () => {
let humanUser
before(() => {
cy.humanSetup()
.then(response => {
humanUser = response.body
})
})
it('Users can log in as a human')
})
})
beforeEach(() => {
cy.visit('/signin/form')
})
describe('Sign up', () => {
beforeEach(() => {
cy.contains('Sign up')
.click()
})
it('Users can create a new account')
// enter email & password
// click submit
// assert 201 response from api
// assert success message in ui
})
describe('Sign in', () => {
describe('Charity', () => {
let charityUser
before(() => {
cy.charitySetup()
.then(response => {
charityUser = response.body
})
})
it('Users can log in as a charity')
// enter email & password
// click submit
// assert 200 response from api
// assert url is charity home screen
})
describe('Human', () => {
let humanUser
before(() => {
cy.humanSetup()
.then(response => {
humanUser = response.body
})
})
it('Users can log in as a human')
// enter email & password
// click submit
// assert 200 response from api
// assert url is human home screen
})
})
Questions via Sli.do #cygofundme
abstraction isn't necessary
it('uses page object model', () => {
login
.visit()
.enterEmail('tester@example.com')
.enterPassword('password')
.submit();
});
it('avoids page object model', () => {
cy.visit('/login');
cy.get('#email')
.type('tester@example.com')
cy.get('#password')
.type('password')
cy.get('#submit')
.click();
});
using POM
just use cy commands
$this->testUtility->enterText(
$this->donateLocators->first_name,
$this->donateEntity->getBillingFirstName()
);
$this->testUtility->enterText(
$this->donateLocators->last_name,
$this->donateEntity->getBillingLastName()
);
$this->testUtility->enterText(
$this->donateLocators->email,
$this->donateEntity->getUserEmail()
);
legacy codebase (php)
public function enterText($element, $text, $verifyText = true, $timeout = null)
{
$timeout = $this->setTimeout($timeout);
if (Driver::isMobileBrowser()) {
$foundElement = $this->waitForElementToAppear($element, $timeout);
} else {
$foundElement = $this->scrollToElement($element, $timeout);
}
$foundElement->clear();
$foundElement->sendKeys($text);
if ($verifyText && !(CONFIG['browser'] == 'FIREFOX' && CONFIG['useSaucelabs'])) {
$this->assertEquals($text, $foundElement->getAttribute("value"));
}
}
legacy codebase (php)
you can mock and stub!
$username = $entity_data['username'];
if ($entity_type == EntityTypes::PROJECT) {
$username .= '/' . $entity_data['project_username'];
} elseif ($entity_type == EntityTypes::FUNDRAISER) {
$entity_type = EntityTypes::PROJECT; // due to $entity_type being used in url below
}
$this->donateEntity->setAmount(100);
$this->donateEntity->setCharityId($entity_data['charity_id']);
$this->donateEntity->setFeeStructureType($entity_data['fee_structure_type']);
if ($this->donateEntity->getPaymentProcessor() == PaymentProcessors::PPGF) {
$this->donateEntity->setCardCvv('324');
} else {
$this->donateEntity->setCardNumber('4111111111111111');
}
if ($widget) {
$fees_data = $this->donateSupport->widgetFeeCalculations($entity_data['charity_id'], $entity_data['fee_id'], $this->donateEntity->getAmount());
} else {
$fees_data = $this->donateSupport->feeCalculations($entity_data['charity_id'], $this->donateEntity->getAmount());
}
it(`
Users can donate to a team:
* Logged out
* PPGF
* USD
* Includes Tip
* $25, one-time
`, () => {
cy.visit(`/donate/project/${fundraiser.team_slug}`);
// step 1
cy.contains('$25')
.click();
cy.contains('Continue')
.click();
// step 2: billing info
cy.get('#first_name')
.type('First');
cy.get('#last_name')
.type('Last');
cy.get('#email')
.type('testdonation@example.com');
cy.get('#address')
.type('123 Main St');
cy.get('#country')
.select('United States');
cy.get('#city')
.type('LA');
cy.get('#state')
.select('CA');
cy.get('#zip')
.type('12345');
// step 2: payment info
cy.get('#shown_card_number')
.type(getRandomPPGFTestCard());
cy.get('#exp_month')
.type('01');
cy.get('#exp_year')
.type('2030');
cy.get('#cvc')
.type('123');
cy.contains('DONATE')
.click();
// success
cy.url()
.should('include', '/share');
});
use before and beforeEach
and nest 'em!
build your own cy.getTestID
Questions via Sli.do #cygofundme
Cypress Best Practices
Cypress.Commands.add('getDataQA', (value) => {
return cy.get(`[data-qa="${value}"]`)
})
support/commands.js
it('works', () => {
cy.getDataQA('like-magic')
.click()
})
some.spec.js
use route aliases or assertions
cy.server()
.route({
method: 'GET',
url: '**/users/*',
})
.as('users')
// ...
cy.wait('@users')
// ...
cy.visit('/react-app')
.get('.loading')
.should('not.be.visible')
// ...
*
Questions via Sli.do #cygofundme
/in/twilliams15