@bahmutov
@joptimizely
Questions via Sli.do #cyopt
@bahmutov
@joptimizely
Experimentation Platform (A/B/n, Personalization, MVT, Stats Acceleration)
Progressive Delivery (Feature Rollouts, Feature Flag Experiments)
Progressive Delivery (Feature Rollouts, Feature Flag Experiments)
defaults: &defaults
script:
- npm run cy:run -- --record --parallel --group $STAGE_NAME
- kill $(jobs -p) || true
jobs:
include:
# we have multiple jobs to execute using just a single stage
# but we can pass group name via environment variable to Cypress test runner
# run tests in parallel by including several test jobs with same name variable
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
- stage: test
env:
- STAGE_NAME=8x-electron
<<: *defaults
Cypress gave me the ability, and confidence, to test our integration within Salesforce
Mike London, QA Engineer - Optimizely
Are you using feature flags right now?
Feature flags (also known as feature toggles or feature switches) are a software development technique that turns certain functionality on and off during runtime, without deploying new code. This allows for better control and more experimentation over the full lifecycle of features.
Do you have tests for your feature flags?
describe('astro_boy Feature Scenarios', function () {
it('Validate that the astro_boy feature is not enabled with no query parameter', function () {
// Visit app
cy.visit('/')
// Validate feature is disabled
cy.get('#astronaut')
.should('not.exist')
})
it('Validate that the astro_boy feature is not enabled with incorrect query parameter', function () {
// Visit app
cy.visit('/?cypress=off')
// Validate feature is disabled
cy.get('#astronaut')
.should('not.exist')
})
it('Validate that the astro_boy feature is enabled', function () {
// Visit app with audience query parameter
cy.visit('/?cypress=on')
// Validate feature is enabled
cy.get('#astronaut')
.should('exist')
})
it('Validate button is clickable', function () {
// Visit app with audience query parameter
cy.visit('/?cypress=on')
// Validate feature is enabled
cy.get('button')
.click()
})
})
Short Term
Feature Rollout
For deploying a new feature that will be permanent.
Long Term
Permission
Change product experience that certain users receive
Operational
Controls operational aspects of your system; allows control of certain operational features in their production env.
Circuit Breaker
Turn on/off certain features or conditions in your production env.
Short Term
Feature Rollout
For deploying a new feature that will be permanent.
Long Term
Permission
Change product experience that certain users receive
Operational
Controls operational aspects of your system; allows control of certain operational features in their production env.
Circuit Breaker
Turn on/off certain features or conditions in your production env.
Short Term
Feature Rollout
For deploying a new feature that will be permanent.
Long Term
Permission
Change product experience that certain users receive
Operational
Controls operational aspects of your system; allows control of certain operational features in their production env.
Circuit Breaker
Turn on/off certain features or conditions in your production env.
What do you do once your feature is rolled out to 100%, but the feature flag has not been removed?
Cypress makes it easy to organize test cases. Once a feature is rolled out we move the legacy test case into a deprecated file.
defaults:
script: &defaults
- npm run cy:run -- --record --parallel --group $STAGE_NAME
- kill $(jobs -p) || true
jobs:
include:
# we have multiple jobs to execute using just a single stage
# but we can pass group name via environment variable to Cypress test runner
# run tests in parallel by including several test jobs with same name variable
- stage: deprecated
env:
- STAGE_NAME=deprecated
script: npm run cy:deprecated -- --record --config integrationFolder=cypress/deprecated
- stage: test
env:
- STAGE_NAME=5x-electron
script: *defaults
- stage: test
env:
- STAGE_NAME=5x-electron
script: *defaults
- stage: test
env:
- STAGE_NAME=5x-electron
script: *defaults
- stage: test
env:
- STAGE_NAME=5x-electron
script: *defaults
- stage: test
env:
- STAGE_NAME=5x-electron
script: *defaults
How do you test different account or permission types?
Cypress makes it easy to create custom commands that allow us to take advantage of our create test account handler.
Cypress.Commands.add('createAccount', (config) => {
config += `&email=bdd%2Btest${ Date.now() }@optimizely.com`
config += `&password1=${ Cypress.env('newAccountPassword') }`
let email = ''
let accountId = null
let projectId = null
let csrfToken = ''
const options = {
url: '/account/test_account_create',
method: 'POST',
body: config,
headers: { 'User-Agent': 'optimizely-e2e-test', 'Cookie': 'green=true' },
form: true
}
cy.log(`Attempting to create account on ${ Cypress.env('baseUrl') }`)
cy.request(options)
.then(response => {
accountId = response.body.account_id
csrfToken = response.body.csrf_token
email = response.body.user_email
projectId = response.body.project_id
cy.log(`Account ${ accountId } created for ${ email }.`)
cy.log('Getting personal access token and setting persistent cookies.')
cy.acceptTOS({ email: email, csrfToken: csrfToken })
cy.getPersonalAccessToken({ email: email, accountId: accountId, csrfToken: csrfToken })
.then(response => {
window.sessionStorage.setItem('accessToken', response.body.token_hash)
persistentStorage.accessToken = response.body.token_hash
})
cy.setCookie('csrftoken', csrfToken)
cy.setCookie('hasSeenABV2FeedbackRecently', 'true')
window.sessionStorage.setItem('accountId', accountId)
window.sessionStorage.setItem('projectId', projectId)
persistentStorage = {
accountId: accountId,
projectId: projectId
}
cy.getCookies()
.then(cookies => {
persistentCookies = cookies
})
})
})
describe('Rollouts Experiments Dashboard Tests', function () {
before(function () {
cy.createAccount(Cypress.env('free_rollout_account'))
.saveGeneratedTimeBasedIdAs('featureKey')
.saveGeneratedTimeBasedIdAs('featureKey1')
.saveGeneratedTimeBasedIdAs('experimentKey')
.saveGeneratedTimeBasedIdAs('experimentKey1')
})
describe('Full Stack Permissions Experiments Dashboard Tests', function () {
before(function () {
cy.createAccount(Cypress.env('full_stack_account'))
.saveGeneratedTimeBasedIdAs('featureKey')
.saveGeneratedTimeBasedIdAs('featureKey1')
.saveGeneratedTimeBasedIdAs('experimentKey')
.saveGeneratedTimeBasedIdAs('experimentKey1')
})
Default Variable
Operational Variable
describe('Astro Boy Operational Flag Tests', function () {
it('Button should have default text when user is not in feature test', () => {
cy.visit('/')
cy.get('img#astronaut')
.should('be.visible')
.and('have.attr', 'alt')
cy.get('button')
.should('have.text', 'Learn More')
.and('be.enabled')
})
it('Button should have updated text when user is in feature test', () => {
cy.visit('/?cypress=on')
cy.get('img#astronaut')
.should('be.visible')
.and('have.attr', 'alt')
cy.get('button')
.should('have.text', 'Blast Off!')
.and('be.enabled')
})
})
describe('Snippet Circuit Breaker Test', () => {
// Validate the pressence of the Optimizely snippet when the flag is on
before(() => {
cy.login()
.setPersistentCookies()
})
it('Contains the Optimizely snippet in the <head> tag with the feature on', () => {
cy.visit(`${Cypress.env('referer')}?cypress=on`)
cy.get('head script[src="//cdn.optimizely.com/public/5935064/s/optimizely_app.js"]')
.should('exist')
})
it('Does not contain the Optimizely snippet in the <head> tag with the feature off', () => {
cy.visit(Cypress.env('referer'))
cy.get('head script[src="//cdn.optimizely.com/public/5935064/s/optimizely_app.js"]')
.should('not.exist')
})
})
Short Term Flags
Experimentation
Allows you to perform A/B tests.
Allows you to make data-driven optimization
Non Critical Variations
Low risk or non important variations
Goal: Don't have to write 2 test cases (feature off/feature on)
Goal: Don't have to write 2 test cases (feature off/feature on)
Solution: Cypress should know which variation (API call to Optimizely) which allows it to know what variation or feature variable
Contact information
Todd: todd.seller@optimizely.com
Jeff: jeffrey.sing@optimizely.com
✨Free Version of Optimizely Feature Flags/Experiments✨
Thank you!