diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 00000000000..17161e32e08
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from "cypress";
+
+export default defineConfig({
+ e2e: {
+ setupNodeEvents(on, config) {
+ // implement node event listeners here
+ },
+ },
+});
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
new file mode 100644
index 00000000000..02e4254378e
--- /dev/null
+++ b/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
new file mode 100644
index 00000000000..698b01a42c3
--- /dev/null
+++ b/cypress/support/commands.ts
@@ -0,0 +1,37 @@
+///
+// ***********************************************
+// This example commands.ts shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+//
+// declare global {
+// namespace Cypress {
+// interface Chainable {
+// login(email: string, password: string): Chainable
+// drag(subject: string, options?: Partial): Chainable
+// dismiss(subject: string, options?: Partial): Chainable
+// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable
+// }
+// }
+// }
\ No newline at end of file
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 00000000000..f80f74f8e1f
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.ts is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
\ No newline at end of file
diff --git a/protocol-designer/cypress/e2e/mixSettings.cy.ts b/protocol-designer/cypress/e2e/mixSettings.cy.ts
new file mode 100644
index 00000000000..85252d762b2
--- /dev/null
+++ b/protocol-designer/cypress/e2e/mixSettings.cy.ts
@@ -0,0 +1,61 @@
+import '../support/commands.ts'; // Importing the custom commands file
+import {
+ Actions,
+ Verifications,
+ runMixSetup,
+} from '../support/mixSetting'
+import { UniversalActions } from '../support/universalActions'
+import { TestFilePath, getTestFile } from '../support/testFiles'
+import {
+ // verifyOldProtocolModal,
+ verifyImportProtocolPage,
+} from '../support/import'
+
+describe('Redesigned Mixing Steps - Happy Path', () => {
+ beforeEach(() => {
+ cy.visit('/')
+ cy.closeAnalyticsModal()
+ const protocol = getTestFile(TestFilePath.DoItAllV8)
+ cy.importProtocol(protocol.path)
+ verifyImportProtocolPage(protocol)
+
+ // NOTE: vv make this chunk better//
+ cy.contains("Edit protocol").click()
+ cy.contains("Protocol steps").click()
+ cy.get('[id="AddStepButton"]').contains("Add Step").click()
+ cy.verifyOverflowBtn()
+ });
+
+
+ it('It should verify the working function of every permutation of mix checkboxes', () => {
+ const steps: Array = [
+ Actions.SelectMix,
+ UniversalActions.Snapshot,
+ Verifications.PartOne,
+ Actions.SelectLabware,
+ Actions.SelectWellInputField,
+ Verifications.WellSelectPopout,
+ UniversalActions.Snapshot,
+ Actions.Save,
+ Actions.EnterVolume,
+ Actions.EnterMixReps,
+ Actions.SelectTipHandling,
+ UniversalActions.Snapshot,
+ Actions.Continue,
+ Verifications.PartTwoAsp,
+ Actions.AspirateFlowRate,
+ Actions.AspWellOrder,
+ Verifications.AspWellOrder,
+ // Actions.Dispense,
+ // Verifications.PartTwoDisp,
+
+ ]
+
+ runMixSetup(steps)
+ // cy.contains('Primary order').closest('div')
+ });
+});
+
+/*
+NEED TO ADD RENAME
+*/
\ No newline at end of file
diff --git a/protocol-designer/cypress/support/commands.ts b/protocol-designer/cypress/support/commands.ts
index 697ddfa32f5..3dfc586999c 100644
--- a/protocol-designer/cypress/support/commands.ts
+++ b/protocol-designer/cypress/support/commands.ts
@@ -32,6 +32,8 @@ declare global {
verifyCreateNewPage: () => Cypress.Chainable
togglePreWetTip: () => Cypress.Chainable
mixaspirate: () => Cypress.Chainable
+ clickConfirm: () => Cypress.Chainable
+ verifyOverflowBtn: () => Cypress.Chainable
}
}
}
@@ -49,6 +51,12 @@ export const content = {
appSettings: 'App Info',
privacy: 'Privacy',
shareSessions: 'Share analytics with Opentrons',
+ move: "Move",
+ transfer: "Transfer",
+ mix: "Mix",
+ pause: "Pause",
+ heaterShaker: "Heater-shaker",
+ thermocyler: "Thermocycler",
}
export const locators = {
@@ -104,11 +112,12 @@ Cypress.Commands.add('verifyCreateNewHeader', () => {
// Home Page
Cypress.Commands.add('verifyHomePage', () => {
cy.contains(content.welcome)
+ cy.get(locators.privacyPolicy).should('exist').and('be.visible')
+ cy.get(locators.eula).should('exist').and('be.visible')
+ cy.contains(locators.confirm).click()
cy.contains('button', locators.createProtocol).should('be.visible')
cy.contains('label', locators.importProtocol).should('be.visible')
cy.getByTestId(locators.settingsDataTestid).should('be.visible')
- cy.get(locators.privacyPolicy).should('exist').and('be.visible')
- cy.get(locators.eula).should('exist').and('be.visible')
})
Cypress.Commands.add('clickCreateNew', () => {
@@ -122,6 +131,10 @@ Cypress.Commands.add('closeAnalyticsModal', () => {
.click({ force: true })
})
+Cypress.Commands.add('clickConfirm', () => {
+ cy.contains(locators.confirm).click()
+})
+
// Header Import
Cypress.Commands.add('importProtocol', (protocolFilePath: string) => {
cy.contains(locators.import).click()
@@ -158,6 +171,15 @@ Cypress.Commands.add('verifySettingsPage', () => {
.should('be.visible')
})
+Cypress.Commands.add('verifyOverflowBtn', () => {
+ cy.contains(content.move).should('exist').should('be.visible')
+ cy.contains(content.transfer).should('exist').should('be.visible')
+ cy.contains(content.mix).should('exist').should('be.visible')
+ cy.contains(content.pause).should('exist').should('be.visible')
+ cy.contains(content.heaterShaker).should('exist').should('be.visible')
+ cy.contains(content.thermocyler).should('exist').should('be.visible')
+})
+
/// /////////////////////////////////////////////////////////////////
// Legacy Code Section
// This code is deprecated and should be removed
@@ -183,9 +205,7 @@ Cypress.Commands.add('openFilePage', () => {
// Pipette Page Actions
//
-Cypress.Commands.add(
- 'choosePipettes',
- (leftPipetteSelector, rightPipetteSelector) => {
+Cypress.Commands.add('choosePipettes', (leftPipetteSelector, rightPipetteSelector) => {
cy.get('[id="PipetteSelect_left"]').click()
cy.get(leftPipetteSelector).click()
cy.get('[id="PipetteSelect_right"]').click()
diff --git a/protocol-designer/cypress/support/mixSetting.ts b/protocol-designer/cypress/support/mixSetting.ts
new file mode 100644
index 00000000000..80fe00fd2d9
--- /dev/null
+++ b/protocol-designer/cypress/support/mixSetting.ts
@@ -0,0 +1,262 @@
+import { contains } from 'cypress/types/jquery'
+import { executeUniversalAction, UniversalActions } from './universalActions'
+import { isEnumValue } from './utils'
+
+export enum Actions {
+ Confirm = 'Confirm',
+ Continue = 'Continue',
+ GoBack = 'Go back',
+ Back = 'Back',
+ Save = 'Save',
+ Edit = 'Edit',
+ SelectMix = "Select Mix",
+ SelectLabware = 'Select on deck labware',
+ SelectWellInputField = 'Select wells',
+ EnterVolume = 'Enter a valid volume to mix',
+ EnterMixReps = 'Enter number of repetions to mix',
+ SelectTipHandling = 'Select how/if tips should be picked up for each mix',
+ AspirateFlowRate = 'Select aspirate settings',
+ Dispense = 'Select dispnse settings',
+ AspWellOrder = 'Open well aspirate well order pop out',
+ EditWellOrder = 'Edit well order selects',
+
+}
+
+export enum Verifications {
+ PartOne = 'Verify Part 1, the configuration of mix settings, and check continue button',
+ PartTwoAsp = 'Verify Part 2, the configuration of asp settings and check go back and save button',
+ PartTwoDisp = 'Verify Part 2, the configuration of disp settings and check go back and save button',
+ WellSelectPopout = 'Verify labware image and available wells',
+ AspWellOrder = 'Verify pop out for well order during aspirate',
+ AspMixTipPos = 'Verify pop out for mix tip position durin aspirate',
+}
+
+export enum Content {
+ Move = 'Move',
+ Transfer = 'Transfer',
+ Mix = 'Mix',
+ Pause = 'Pause',
+ HeaterShaker = 'Heater-shaker',
+ Thermocyler = 'Thermocycler',
+ Pipette = 'Pipette',
+ Tiprack = 'Tiprack',
+ Labware = 'Labware',
+ SelectWells = 'Select wells',
+ VolumePerWell = 'Volume per well',
+ MixRepetitions = 'Mix repetitions',
+ TipHandling = 'Tip handling',
+ TipDropLocation = 'Tip drop location',
+ ChooseOption = 'Choose option',
+ Reservoir = 'Axygen 1 Well Reservoir 90 mL',
+ WellPlate = 'Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt',
+ PartOne = 'Part 1 / 2',
+ PartTwo = 'Part 2 / 2',
+ WellSelectTitle = 'Select wells using a Flex 1-Channel 1000 μL',
+ ClickAndDragWellSelect = "Click and drag to select wells",
+ PipettePreselect = 'Flex 1-Channel 1000 μL',
+ TiprackPreselect = 'Opentrons Flex 96 Tip Rack 1000 µL',
+ BeforeEveryAsp = 'Before every aspirate',
+ OnceAtStartStep = 'Once at the start of step',
+ PerSourceWell = 'Per source well',
+ PerDestWell = 'Per destination well',
+ Never = 'Never',
+ WasteChute = 'Waste chute',
+ AspFlowRate = 'Aspirate flow rate',
+ AspWellOrder = 'Aspirate well order',
+ MixTipPosition = 'Mix tip position',
+ AdvancedPipSettings = 'Advanced pipetting settings',
+ Delay = 'Delay',
+ DelayDuration = 'Delay Duration',
+ DispFlowRate = "Dispense flow rate",
+ Blowout = 'Blowout',
+ TouchTip = 'Touch tip',
+ TopBottomLeRi = 'Top to bottom, Left to right',
+ EditWellOrder = 'Edit well order',
+ WellOrderDescrip = 'Change how the robot moves from well to well.',
+ PrimaryOrder = 'Primary order',
+ TopToBottom = 'Top to bottom',
+ BottomToTop = 'Bottom to top',
+ LeftToRight = 'Left to right',
+ RightToLeft = 'Right to left',
+ Then = 'then',
+ SecondaryOrder = 'Secondary order',
+ Cancel = 'Cancel',
+}
+
+export enum Locators {
+ Continue = 'button:contains("Continue")',
+ GoBack = 'button:contains("Go back")',
+ Back = 'button:contains("Back")',
+ WellInputField = '[name="wells"]',
+ Save = 'button:contains("Save")',
+ OneWellReservoirImg = '[data-wellname="A1"]',
+ Volume = '[name="volume"]',
+ MixReps = '[name="times"]',
+ Aspirate = 'button:contains("Aspirate")',
+ Dispense = 'button:contains("Dispense")',
+ AspFlowRateInput = '[name="aspirate_flowRate"]',
+ AspWellOrder = '[class="Flex-sc-1qhp8l7-0 ListButton___StyledFlex-sc-1lmhs3v-0 bToGfF bdMeyp"]',
+ ResetToDefault = 'button:contains("Reset to default")',
+ PrimaryOrderDropdown = 'div[tabindex="0"].sc-bqWxrE jKLbYH iFjNDq',
+ CancelWellOrder = '[class="SecondaryButton-sc-1opt1t9-0 kjpcRL"]',
+}
+
+
+const executeAction = (action: Actions | UniversalActions): void => {
+ if (isEnumValue([UniversalActions], [action])) {
+ executeUniversalAction(action as UniversalActions)
+ return
+ }
+
+
+
+ switch (action) {
+ case Actions.SelectMix:
+ cy.get('button').contains('Mix').click()
+ break
+ case Actions.SelectLabware:
+ cy.contains(Content.ChooseOption).should('be.visible').click()
+ cy.contains(Content.Reservoir).should('be.visible').click()
+ break
+ case Actions.SelectWellInputField:
+ cy.get(Locators.WellInputField).should('be.visible').click()
+ break
+ case Actions.EnterVolume:
+ cy.get(Locators.Volume).should('exist').type('100')
+ break
+ case Actions.EnterMixReps:
+ cy.get(Locators.MixReps).should('exist').type('5')
+ break
+ case Actions.SelectTipHandling:
+ cy.contains(Content.BeforeEveryAsp).should('exist').should('be.visible').click()
+ cy.contains(Content.OnceAtStartStep).should('exist').should('be.visible')
+ cy.contains(Content.PerSourceWell).should('exist').should('be.visible')
+ cy.contains(Content.PerDestWell).should('exist').should('be.visible')
+ cy.contains(Content.Never).should('exist').should('be.visible')
+ cy.contains(Content.OnceAtStartStep).click()
+ break
+ case Actions.AspirateFlowRate:
+ cy.get(Locators.Aspirate).should('exist').should('be.visible').click()
+ cy.get(Locators.AspFlowRateInput).should('exist')
+ cy.get(Locators.AspFlowRateInput).type('{selectAll}, {backspace}, 100')
+ break
+ case Actions.AspWellOrder:
+ cy.contains(Content.TopBottomLeRi).should('exist').should('be.visible')
+ cy.get(Locators.AspWellOrder).click()
+ break
+ case Actions.Dispense:
+ cy.get(Locators.Dispense).should('exist').should('be.visible').click()
+ break
+ // case Actions.FlowRateWarning:
+ // break
+ case Actions.Save:
+ cy.get(Locators.Save).should('exist').should('be.visible').click()
+ break
+ case Actions.Back:
+ cy.get(Locators.Back).should('exist').should('be.visible').click()
+ break
+ case Actions.Continue:
+ cy.get(Locators.Continue).should('exist').should('be.visible').click({force: true})
+ break
+ default:
+ throw new Error(`Unrecognized action: ${action as string}`)
+ }
+}
+
+const verifyStep = (verification: Verifications): void => {
+ switch (verification) {
+ case Verifications.PartOne:
+ cy.contains(Content.PartOne).should('exist').should('be.visible')
+ cy.contains(Content.Mix).should('exist').should('be.visible')
+ cy.contains(Content.Pipette).should('exist').should('be.visible')
+ cy.contains(Content.PipettePreselect).should('exist').should('be.visible')
+ cy.contains(Content.Tiprack).should('exist').should('be.visible')
+ cy.contains(Content.TiprackPreselect).should('exist').should('be.visible')
+ cy.contains(Content.Labware).should('exist').should('be.visible')
+ cy.contains(Content.SelectWells).should('exist').should('be.visible')
+ cy.contains(Content.VolumePerWell).should('exist').should('be.visible')
+ cy.contains(Content.MixRepetitions).should('exist').should('be.visible')
+ cy.contains(Content.TipHandling).should('exist').should('be.visible')
+ cy.contains(Content.TipDropLocation).should('exist').should('be.visible')
+ cy.contains(Content.WasteChute).should('exist').should('be.visible')
+ cy.get(Locators.Continue).should('exist').should('be.visible')
+ break
+ case Verifications.WellSelectPopout:
+ cy.contains(Content.WellSelectTitle).should('exist').should('be.visible')
+ cy.contains(Content.ClickAndDragWellSelect).should('exist').should('be.visible')
+ cy.get(Locators.OneWellReservoirImg).should('exist').should('be.visible')
+ cy.get(Locators.Save).should('exist').should('be.visible')
+ cy.get(Locators.Back).should('exist').should('be.visible')
+ break
+ case Verifications.PartTwoAsp:
+ cy.contains(Content.PartTwo).should('exist').should('be.visible')
+ cy.contains(Content.Mix).should('exist').should('be.visible')
+ cy.get(Locators.Aspirate).should('exist').should('be.visible')
+ cy.contains(Content.AspFlowRate).should('exist').should('be.visible')
+ cy.contains(Content.AspWellOrder).should('exist').should('be.visible')
+ cy.contains(Content.MixTipPosition).should('exist').should('be.visible')
+ cy.contains(Content.AdvancedPipSettings).should('exist').should('be.visible')
+ cy.contains(Content.Delay).should('exist').should('be.visible')
+ cy.get(Locators.Back).should('exist').should('be.visible')
+ cy.get(Locators.Save).should('exist').should('be.visible')
+ break
+ case Verifications.PartTwoDisp:
+ cy.contains(Content.PartTwo).should('exist').should('be.visible')
+ cy.contains(Content.Mix).should('exist').should('be.visible')
+ cy.get(Locators.Dispense).should('exist').should('be.visible')
+ cy.contains(Content.DispFlowRate).should('exist').should('be.visible')
+ cy.contains(Content.AdvancedPipSettings).should('exist').should('be.visible')
+ cy.contains(Content.Delay).should('exist').should('be.visible')
+ cy.contains(Content.Blowout).should('exist').should('be.visible')
+ cy.contains(Content.TouchTip).should('exist').should('be.visible')
+ break
+ case Verifications.AspWellOrder:
+ cy.contains(Content.EditWellOrder).should('exist').should('be.visible')
+ cy.contains(Content.WellOrderDescrip).should('exist').should('be.visible')
+ cy.contains(Content.PrimaryOrder).should('exist').should('be.visible')
+ cy.contains(Content.TopToBottom).should('exist').should('be.visible').click()
+ cy.contains(Content.BottomToTop).should('exist').should('be.visible')
+ cy.contains(Content.LeftToRight).should('exist').should('be.visible')
+ cy.contains(Content.RightToLeft).should('exist').should('be.visible')
+ cy.contains(Content.BottomToTop).should('exist').should('be.visible').click()
+ cy.contains(Content.Then).should('exist').should('be.visible')
+ cy.contains(Content.SecondaryOrder).should('exist').should('be.visible')
+ cy.contains(Content.LeftToRight).should('exist').should('be.visible').click()
+ cy.contains(Content.RightToLeft).should('exist').should('be.visible').click()
+ cy.get(Locators.ResetToDefault).click()
+ cy.contains(Content.TopToBottom).should('exist').should('be.visible')
+ cy.contains(Content.LeftToRight).should('exist').should('be.visible')
+ cy.get(Locators.CancelWellOrder).should('exist').should('be.visible')
+ cy.get(Locators.Save).should('exist').should('be.visible')
+ break
+ // case Verifications.MixTipPos:
+ // break
+ // case Verifications.FlowRateRangeWarning:
+ // break
+ default:
+ throw new Error(
+ `Unrecognized verification: ${verification as Verifications}`
+ )
+ }
+}
+
+export const runMixSetup = (
+ steps: Array
+): void => {
+ const enumsToCheck = [Actions, Verifications, UniversalActions]
+
+ if (!isEnumValue(enumsToCheck, steps)) {
+ throw new Error('One or more steps are unrecognized.')
+ }
+
+ steps.forEach(step => {
+ if (isEnumValue([Actions], step)) {
+ executeAction(step as Actions)
+ } else if (isEnumValue([Verifications], step)) {
+ verifyStep(step as Verifications)
+ } else if (isEnumValue([UniversalActions], step)) {
+ executeAction(step as UniversalActions)
+ }
+ })
+}
+