From 292b5d7dbcafafd2c9187419966291d744fc15a8 Mon Sep 17 00:00:00 2001 From: Tomer Date: Wed, 4 Apr 2018 14:26:27 +0300 Subject: [PATCH 01/23] added control flow commands to autocomplete --- packages/selenium-ide/src/neo/models/Command.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index ea83c8e910..59d928eb66 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -106,14 +106,21 @@ export const Commands = Object.freeze({ assertText: "assert text", assertTitle: "assert title", assertValue: "assert value", + break: "break", chooseCancelOnNextConfirmation: "choose cancel on next confirmation", chooseCancelOnNextPrompt: "choose cancel on next prompt", chooseOkOnNextConfirmation: "choose ok on next confirmation", clickAt: "click at", + continue: "continue", + do: "do", doubleClickAt: "double click at", dragAndDropToObject: "drag and drop to object", echo: "echo", editContent: "edit content", + else: "else", + end: "end", + endDo: "endDo", + if: "if", mouseDownAt: "mouse down at", mouseMoveAt: "mouse move at", mouseOut: "mouse out", @@ -132,6 +139,7 @@ export const Commands = Object.freeze({ storeText: "store text", storeTitle: "store title", submit: "submit", + times: "times", type: "type", verifyChecked: "verify checked", verifyNotChecked: "verify not checked", @@ -147,7 +155,8 @@ export const Commands = Object.freeze({ webdriverAnswerOnNextPrompt: "webdriver answer on next prompt", webdriverChooseCancelOnNextConfirmation: "webdriver choose cancel on next confirmation", webdriverChooseCancelOnNextPrompt: "webdriver choose cancel on next prompt", - webdriverChooseOkOnNextConfirmation: "webdriver choose ok on next confirmation" + webdriverChooseOkOnNextConfirmation: "webdriver choose ok on next confirmation", + while: "while" }); export const CommandsArray = Object.freeze(Object.keys(Commands)); From b96ed99155ffae5555e59eaf087c65d1319bdcd4 Mon Sep 17 00:00:00 2001 From: Tomer Date: Wed, 4 Apr 2018 14:48:46 +0300 Subject: [PATCH 02/23] indentation --- packages/selenium-ide/src/neo/components/TestRow/index.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/components/TestRow/index.jsx b/packages/selenium-ide/src/neo/components/TestRow/index.jsx index c09933eb93..a3ff29a7c6 100644 --- a/packages/selenium-ide/src/neo/components/TestRow/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestRow/index.jsx @@ -106,6 +106,7 @@ class TestRow extends React.Component { command: PropTypes.string.isRequired, target: PropTypes.string, value: PropTypes.string, + level: PropTypes.number, isBreakpoint: PropTypes.bool, toggleBreakpoint: PropTypes.func, onClick: PropTypes.func, @@ -200,6 +201,7 @@ class TestRow extends React.Component { //setting component of context menu. this.props.setContextMenu(listMenu); + const index = this.props.index >= 0 ? {this.props.index + 1}. : null; const rendered = {return(this.node = node || this.node);}} className={classNames(this.props.className, {"selected": this.props.selected}, {"break-point": this.props.isBreakpoint}, {"dragging": this.props.dragInProgress})} @@ -214,12 +216,12 @@ class TestRow extends React.Component { }}> {this.props.comment ? - {this.props.index >= 0 ? {this.props.index + 1}. : null} + {index} {this.props.comment} : - {this.props.index >= 0 ? {this.props.index + 1}. : null} + {index} {this.props.command} {this.props.target} From a901f3efa2eee5e218dba8546f3e447f39e42164 Mon Sep 17 00:00:00 2001 From: Tomer Date: Wed, 4 Apr 2018 16:43:58 +0300 Subject: [PATCH 03/23] indent the commands in the table --- .../src/neo/components/TestTable/index.jsx | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/packages/selenium-ide/src/neo/components/TestTable/index.jsx b/packages/selenium-ide/src/neo/components/TestTable/index.jsx index ea905b026b..8762cfcd64 100644 --- a/packages/selenium-ide/src/neo/components/TestTable/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestTable/index.jsx @@ -40,6 +40,7 @@ export default class TestTable extends React.Component { clearAllCommands: PropTypes.func }; render() { + let level = 0; return ([
@@ -55,8 +56,9 @@ export default class TestTable extends React.Component {
- { this.props.commands ? this.props.commands.map((command, index) => ( - { + if (isBlockEnd(command.command) && level > 0) level--; + const row = { UiState.copyToClipboard(command); }} clearAllCommands={this.props.clearAllCommands} setSectionFocus={UiState.setSectionFocus} - /> - )).concat( + />; + if (isBlock(command.command)) level++; + return row; + }).concat( Date: Tue, 15 May 2018 00:29:20 +0300 Subject: [PATCH 04/23] Playback tree class --- .../src/neo/IO/SideeX/playback.js | 15 ++++- .../src/neo/IO/SideeX/playbackTree.js | 65 +++++++++++++++++++ 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index f57e7d56cc..29ccc1e9b2 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -20,6 +20,7 @@ import PlaybackState, { PlaybackStates } from "../../stores/view/PlaybackState"; import UiState from "../../stores/view/UiState"; import ExtCommand, { isExtCommand } from "./ext-command"; import { xlateArgument } from "./formatCommand"; +import PlaybackTree from "./playbackTree"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -51,6 +52,14 @@ function playAfterConnectionFailed() { } function executionLoop() { + console.log('PlaybackState.runningQueue', PlaybackState.runningQueue); + const playbackTree = new PlaybackTree(PlaybackState.runningQueue); + console.log('playbackTree: ', playbackTree ); + console.log('playbackTree.executionNodes', playbackTree.executionNodes); + executionLoopImpl(); +} + +function executionLoopImpl() { (PlaybackState.currentPlayingIndex < 0) ? PlaybackState.setPlayingIndex(0) : PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex + 1); // reached the end if (PlaybackState.currentPlayingIndex >= PlaybackState.runningQueue.length && PlaybackState.isPlaying) return false; @@ -68,11 +77,11 @@ function executionLoop() { (extCommand["do" + upperCase](parsedTarget, value)) .then(() => { PlaybackState.setCommandState(id, PlaybackStates.Passed); - }).then(executionLoop) + }).then(executionLoopImpl) )); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); - return executionLoop(); + return executionLoopImpl(); } else { return doPreparation() .then(doPrePageWait) @@ -81,7 +90,7 @@ function executionLoop() { .then(doDomWait) .then(doDelay) .then(doCommand) - .then(executionLoop); + .then(executionLoopImpl); } } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js new file mode 100644 index 0000000000..6391e46215 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -0,0 +1,65 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +export default class PlaybackTree{ + constructor(commandsArray) { + this.commands = commandsArray; + this.execute(); + } + + execute() { + for(let i = this.commands.length; i>=0; i--) { + if (this.isControlFlowFunction(this.commands[i].command)) { + this.commands[i].setLeft(undefined); + this.commands[i].setRight(this.commands[i+1]); + // TODO: do not rely on left and right to be undefined on closing execution + } else { + // TODO: process control flow command and determine left and right for them + } + } + this.executionNodes = this.commands; + } + + isControlFlowFunction(command) { + return this.isBlock(command) || this.isBlockEnd(command); + } + + isBlock(command) { + switch(command) { + case "if": + case "do": + case "while": + case "times": + case "else": + return true; + default: + return false; + } + } + + isBlockEnd(command) { + switch(command) { + case "end": + case "endDo": + case "else": + return true; + default: + return false; + } + } + +} \ No newline at end of file From 956aff3f160d545dff7e9a639ed3f08d7e23650b Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Tue, 15 May 2018 00:30:00 +0300 Subject: [PATCH 05/23] Command left and right options --- packages/selenium-ide/src/neo/models/Command.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 59d928eb66..f86ba17550 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -25,6 +25,8 @@ export default class Command { @observable target = ""; @observable value = ""; @observable isBreakpoint = false; + @observable left = {}; + @observable right = {}; constructor(id = uuidv4()) { this.id = id; @@ -68,6 +70,14 @@ export default class Command { this.isBreakpoint = !this.isBreakpoint; } + @action.bound setLeft(node) { + this.left = node; + } + + @action.bound setRight(node) { + this.right = node; + } + export() { return { id: this.id, From a1ca4a1e891aeed9aab757a244866b6a2a4a67ce Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Tue, 15 May 2018 02:42:30 +0300 Subject: [PATCH 06/23] Set monads'ish left/right directions for control flow operators --- .../src/neo/IO/SideeX/playback.js | 4 +- .../src/neo/IO/SideeX/playbackTree.js | 77 ++++++++++++++++--- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 29ccc1e9b2..1458e59d1e 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -52,10 +52,8 @@ function playAfterConnectionFailed() { } function executionLoop() { - console.log('PlaybackState.runningQueue', PlaybackState.runningQueue); const playbackTree = new PlaybackTree(PlaybackState.runningQueue); - console.log('playbackTree: ', playbackTree ); - console.log('playbackTree.executionNodes', playbackTree.executionNodes); + playbackTree.maintenance(); executionLoopImpl(); } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 6391e46215..bfeddf9e86 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -15,35 +15,81 @@ // specific language governing permissions and limitations // under the License. + +// For now it iterates through commands array twice. Not sure how to avoid that for now + export default class PlaybackTree{ constructor(commandsArray) { this.commands = commandsArray; - this.execute(); + this.process(); } - execute() { - for(let i = this.commands.length; i>=0; i--) { - if (this.isControlFlowFunction(this.commands[i].command)) { - this.commands[i].setLeft(undefined); + process() { + // TODO: Make sure to clone commands in order to skip re-renders on UI + let endBlockIndexes = []; + let blocks = []; + for(let i = this.commands.length-1; i>=0; i--) { + if (!this.isControlFlowFunction(this.commands[i].command)) { this.commands[i].setRight(this.commands[i+1]); + this.commands[i].setLeft(undefined); // TODO: do not rely on left and right to be undefined on closing execution } else { - // TODO: process control flow command and determine left and right for them + this.inverseControlFlowSwitcher(this.commands[i], i, endBlockIndexes); + } + } + + for(let i = 0; i < this.commands.length; i++) { + if (this.isControlFlowFunction(this.commands[i].command)) { + this.controlFlowSwitcher(this.commands[i], i, blocks); } } + this.executionNodes = this.commands; } + inverseControlFlowSwitcher(command, index, endBlockIndexes) { + switch(command.command) { + case "if": + case "while": + command.setRight(this.commands[index+1]); + command.setLeft(this.commands(endBlockIndexes.pop() + 1)); + break; + case "else": + command.setRight(this.commands(endBlockIndexes.pop() + 1)); + command.setLeft(undefined); + endBlockIndexes.push(index); + break; + case "end": + command.setLeft(undefined); + endBlockIndexes.push(index); + } + } + + controlFlowSwitcher(command, index, blocks) { + if (["if", "else", "while"].includes(command.command)) { + blocks.push(command); + } else if (command.command === "end") { + let lastBlock = blocks.pop(); + if(["if", "else"].includes(lastBlock.command)) { + command.setRight(this.commands[index+1]); + } else if (lastBlock.command === "while") { + command.setRight(lastBlock); + } + } else { + throw new Error("Unknown control flow operator"); + } + } + + // TODO: avoid duplicates with TestTable isControlFlowFunction(command) { return this.isBlock(command) || this.isBlockEnd(command); } + // TODO: do, times isBlock(command) { switch(command) { case "if": - case "do": case "while": - case "times": case "else": return true; default: @@ -51,10 +97,10 @@ export default class PlaybackTree{ } } + // TODO: endDo isBlockEnd(command) { switch(command) { case "end": - case "endDo": case "else": return true; default: @@ -62,4 +108,17 @@ export default class PlaybackTree{ } } + // TODO: maintenance function: remove when possible + maintenance() { + + runCommand(this.executionNodes[0]); + + function runCommand(command) { + console.log(command.command); + if (command.right) { + runCommand(command.right); + } + } + } + } \ No newline at end of file From d16aaeb32c9eb6a9d59de61d216afb8ae870ce4d Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Tue, 15 May 2018 16:48:13 +0300 Subject: [PATCH 07/23] Correct direction determination for control flow commands --- .../src/neo/IO/SideeX/playbackTree.js | 65 ++++++++++++------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index bfeddf9e86..16418dbeca 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -34,49 +34,52 @@ export default class PlaybackTree{ this.commands[i].setLeft(undefined); // TODO: do not rely on left and right to be undefined on closing execution } else { - this.inverseControlFlowSwitcher(this.commands[i], i, endBlockIndexes); + this.inverseControlFlowSwitcher(i, endBlockIndexes); } } for(let i = 0; i < this.commands.length; i++) { if (this.isControlFlowFunction(this.commands[i].command)) { - this.controlFlowSwitcher(this.commands[i], i, blocks); + this.controlFlowSwitcher(i, blocks); } } this.executionNodes = this.commands; } - inverseControlFlowSwitcher(command, index, endBlockIndexes) { - switch(command.command) { + inverseControlFlowSwitcher(index, endBlockIndexes) { + switch(this.commands[index].command) { case "if": case "while": - command.setRight(this.commands[index+1]); - command.setLeft(this.commands(endBlockIndexes.pop() + 1)); + this.commands[index].setRight(this.commands[index+1]); + this.commands[index].setLeft(this.commands[endBlockIndexes.pop() + 1]); break; case "else": - command.setRight(this.commands(endBlockIndexes.pop() + 1)); - command.setLeft(undefined); + this.commands[index].setRight(this.commands[endBlockIndexes.pop() + 1]); + this.commands[index].setLeft(undefined); endBlockIndexes.push(index); break; case "end": - command.setLeft(undefined); + this.commands[index].setLeft(undefined); endBlockIndexes.push(index); + break; + default: + window.addLog(`Unknown control flow operator "${this.commands[index].command}"`); } } - controlFlowSwitcher(command, index, blocks) { - if (["if", "else", "while"].includes(command.command)) { - blocks.push(command); - } else if (command.command === "end") { + controlFlowSwitcher(index, blocks) { + if (["if", "else", "while"].includes(this.commands[index].command)) { + blocks.push(this.commands[index]); + } else if (this.commands[index].command === "end") { let lastBlock = blocks.pop(); if(["if", "else"].includes(lastBlock.command)) { - command.setRight(this.commands[index+1]); + this.commands[index].setRight(this.commands[index+1]); } else if (lastBlock.command === "while") { - command.setRight(lastBlock); + this.commands[index].setRight(lastBlock); } } else { - throw new Error("Unknown control flow operator"); + window.addLog(`Unknown control flow operator "${this.commands[index].command}"`); } } @@ -111,14 +114,30 @@ export default class PlaybackTree{ // TODO: maintenance function: remove when possible maintenance() { - runCommand(this.executionNodes[0]); - - function runCommand(command) { - console.log(command.command); - if (command.right) { - runCommand(command.right); + // runCommand(this.executionNodes[0]); + window.addLog("maintenance"); + this.executionNodes.forEach((node) => { + window.addLog(`[[ Command: ]] ${node.command}`); + if (node.left) { + window.addLog(`[[ Command left direction: ]] ${node.left.command}`); + } else { + window.addLog(`[[ Command left direction: ]] not defined`); } - } + if (node.right) { + window.addLog(`[[ Command right direction: ]] ${node.right.command}`); + } else { + window.addLog(`[[ Command right direction: ]] not defined`); + } + window.addLog(`----------------------`); + }); + + // function runCommand(command) { + // window.addLog() + // console.log(command.command); + // if (command.right) { + // runCommand(command.right); + // } + // } } } \ No newline at end of file From 3348a29ec19e8d360896e5669277f5bbeef5f5a7 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Thu, 17 May 2018 13:53:32 +0300 Subject: [PATCH 08/23] rerenamed executionLoop --- packages/selenium-ide/src/neo/IO/SideeX/playback.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 1458e59d1e..e79dd062a5 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -53,11 +53,7 @@ function playAfterConnectionFailed() { function executionLoop() { const playbackTree = new PlaybackTree(PlaybackState.runningQueue); - playbackTree.maintenance(); - executionLoopImpl(); -} - -function executionLoopImpl() { + // playbackTree.maintenance(); (PlaybackState.currentPlayingIndex < 0) ? PlaybackState.setPlayingIndex(0) : PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex + 1); // reached the end if (PlaybackState.currentPlayingIndex >= PlaybackState.runningQueue.length && PlaybackState.isPlaying) return false; @@ -75,11 +71,11 @@ function executionLoopImpl() { (extCommand["do" + upperCase](parsedTarget, value)) .then(() => { PlaybackState.setCommandState(id, PlaybackStates.Passed); - }).then(executionLoopImpl) + }).then(executionLoop) )); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); - return executionLoopImpl(); + return executionLoop(); } else { return doPreparation() .then(doPrePageWait) @@ -88,7 +84,7 @@ function executionLoopImpl() { .then(doDomWait) .then(doDelay) .then(doCommand) - .then(executionLoopImpl); + .then(executionLoop); } } @@ -245,6 +241,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { : extCommand.doType(xlateArgument(target), xlateArgument(value)) )) .then(function(result) { + window.addLog(`result ${JSON.stringify(result, null, 4)}`); if (result.result !== "success") { // implicit if (result.result.match(/Element[\s\S]*?not found/)) { From 556d0b9fd08eac53b3da874e83a9462718f2eeb1 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Fri, 18 May 2018 07:09:31 +0300 Subject: [PATCH 09/23] Incorporate playback tree to playback execution loop --- .../selenium-ide/src/content/commands-api.js | 32 +++++++++++++ .../selenium-ide/src/content/selenium-api.js | 8 ++++ .../src/neo/IO/SideeX/playback.js | 47 +++++++++++++++---- .../src/neo/IO/SideeX/playbackTree.js | 4 ++ 4 files changed, 82 insertions(+), 9 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index d630ea3881..84107113c5 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -41,6 +41,8 @@ function doCommands(request, sender, sendResponse) { } else if (request.commands == "domWait") { selenium["doDomWait"]("", selenium.preprocessParameter("")); sendResponse({ dom_time: window.sideex_new_page }); + } else if (isConditinal(request.commands)) { + executeConditional(request, sendResponse); } else { const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); if (selenium["do" + upperCase] != null) { @@ -112,6 +114,36 @@ function doCommands(request, sender, sendResponse) { } } +function isConditinal(commandName) { + return ["if", "while"].includes(commandName); +} + +function executeConditional(request, sendResponse) { + + const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); + if (selenium["do" + upperCase] != null) { + try { + document.body.setAttribute("SideeXPlayingFlag", true); + let returnValue = selenium["do"+upperCase](request.target,selenium.preprocessParameter(request.value)); + // conditional command executed! + if (returnValue) { + document.body.removeAttribute("SideeXPlayingFlag"); + sendResponse({result: "truthy"}); + } else { + document.body.removeAttribute("SideeXPlayingFlag"); + sendResponse({result: "falsy"}); + } + } catch(e) { + // Synchronous command failed + document.body.removeAttribute("SideeXPlayingFlag"); + sendResponse({result: e.message}); + } + } else { + sendResponse({ result: "Unknown command: " + request.commands }); + } + +} + if (!window._listener) { window._listener = doCommands; browser.runtime.onMessage.addListener(doCommands); diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index cc64804799..fef629ac21 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -428,6 +428,14 @@ Selenium.prototype.doVerifyElementPresent = function(locator) { } }; +Selenium.prototype.doIf = function(string) { + return eval(string); +}; + +Selenium.prototype.doEnd = function() { + return true; +}; + Selenium.prototype.doVerifyElementNotPresent = function(locator) { try { this.browserbot.findElement(locator); diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index e79dd062a5..c9f6089835 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -38,6 +38,7 @@ let ignoreBreakpoint = false; function play(currUrl) { baseUrl = currUrl; + PlaybackTree.processCommands(PlaybackState.runningQueue); prepareToPlay() .then(executionLoop) .then(finishPlaying) @@ -52,9 +53,9 @@ function playAfterConnectionFailed() { } function executionLoop() { - const playbackTree = new PlaybackTree(PlaybackState.runningQueue); - // playbackTree.maintenance(); - (PlaybackState.currentPlayingIndex < 0) ? PlaybackState.setPlayingIndex(0) : PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex + 1); + if (PlaybackState.currentPlayingIndex < 0) { + PlaybackState.setPlayingIndex(0); + } // reached the end if (PlaybackState.currentPlayingIndex >= PlaybackState.runningQueue.length && PlaybackState.isPlaying) return false; const { id, command, target, value, isBreakpoint } = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex]; @@ -71,11 +72,14 @@ function executionLoop() { (extCommand["do" + upperCase](parsedTarget, value)) .then(() => { PlaybackState.setCommandState(id, PlaybackStates.Passed); - }).then(executionLoop) + return doControlFlow(true); + }).then(doDelay).then(executionLoop) )); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); - return executionLoop(); + return doControlFlow(true) + .then(doDelay) + .then(executionLoop); } else { return doPreparation() .then(doPrePageWait) @@ -84,10 +88,32 @@ function executionLoop() { .then(doDomWait) .then(doDelay) .then(doCommand) + .then(doControlFlow) .then(executionLoop); } } +function doControlFlow(result) { + let nextPlayingIndex; + if (result) { + // right + if (PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].right) { + nextPlayingIndex = PlaybackState.runningQueue.indexOf(PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].right); + } else { + nextPlayingIndex = PlaybackState.runningQueue.length + 1; + } + } else { + // left + if (PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].left) { + nextPlayingIndex = PlaybackState.runningQueue.indexOf(PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].left); + } else { + nextPlayingIndex = PlaybackState.runningQueue.length + 1; + } + } + PlaybackState.setPlayingIndex(nextPlayingIndex); + return Promise.resolve(); +} + function prepareToPlay() { PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex - 1); return extCommand.init(); @@ -104,7 +130,6 @@ function finishPlaying() { function catchPlayingError(message) { if (isReceivingEndError(message)) { setTimeout(function() { - PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex - 1); playAfterConnectionFailed(); }, 100); } else { @@ -145,7 +170,7 @@ reaction( function doPreparation() { return extCommand.sendMessage("waitPreparation", "", "") .then(function() { - return true; + return Promise.resolve(); }); } @@ -241,8 +266,11 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { : extCommand.doType(xlateArgument(target), xlateArgument(value)) )) .then(function(result) { - window.addLog(`result ${JSON.stringify(result, null, 4)}`); - if (result.result !== "success") { + if (result.result === "truthy") { + return true; + } else if (result.result === "falsy") { + return false; + } else if (result.result !== "success") { // implicit if (result.result.match(/Element[\s\S]*?not found/)) { if (implicitTime && (Date.now() - implicitTime > 30000)) { @@ -262,6 +290,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { PlaybackState.setCommandState(id, PlaybackStates.Failed, result.result); } else { PlaybackState.setCommandState(id, PlaybackStates.Passed); + return true; } }); } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 16418dbeca..0062da4d9a 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -111,6 +111,10 @@ export default class PlaybackTree{ } } + static processCommands(commandsArray) { + new PlaybackTree(commandsArray); + } + // TODO: maintenance function: remove when possible maintenance() { From fed110d97403f9e70f89529299a4ccf59fefc0e2 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 00:40:49 +0300 Subject: [PATCH 10/23] elseIf command --- .../selenium-ide/src/content/commands-api.js | 2 +- .../selenium-ide/src/content/selenium-api.js | 12 +++++ .../src/neo/IO/SideeX/playbackTree.js | 48 +++++++++++++++---- .../selenium-ide/src/neo/models/Command.js | 3 +- 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 84107113c5..da8b01fbae 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -115,7 +115,7 @@ function doCommands(request, sender, sendResponse) { } function isConditinal(commandName) { - return ["if", "while"].includes(commandName); + return ["if", "while", "elseIf"].includes(commandName); } function executeConditional(request, sendResponse) { diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index fef629ac21..4e1f8d5680 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -432,6 +432,18 @@ Selenium.prototype.doIf = function(string) { return eval(string); }; +Selenium.prototype.doWhile = function(string) { + return eval(string); +}; + +Selenium.prototype.doElseIf = function(string) { + return eval(string); +}; + +Selenium.prototype.doElse = function() { + return true; +}; + Selenium.prototype.doEnd = function() { return true; }; diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 0062da4d9a..e269cc0066 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -26,14 +26,12 @@ export default class PlaybackTree{ process() { // TODO: Make sure to clone commands in order to skip re-renders on UI + let endIndexes = []; let endBlockIndexes = []; let blocks = []; for(let i = this.commands.length-1; i>=0; i--) { - if (!this.isControlFlowFunction(this.commands[i].command)) { - this.commands[i].setRight(this.commands[i+1]); - this.commands[i].setLeft(undefined); - // TODO: do not rely on left and right to be undefined on closing execution - } else { + this.inverseExtCommandSwitcher(i, endIndexes); + if (this.isControlFlowFunction(this.commands[i].command)) { this.inverseControlFlowSwitcher(i, endBlockIndexes); } } @@ -47,9 +45,40 @@ export default class PlaybackTree{ this.executionNodes = this.commands; } + inverseExtCommandSwitcher(index, endIndexes) { + if (!this.isControlFlowFunction(this.commands[index].command)) { + // commands preceding 'else', 'elseIf' or 'end' will point to appropriate end in the right + if (endIndexes.length > 0 && this.isBlockEnd(this.commands[index+1].command)) { + this.commands[index].setRight(this.commands[endIndexes[endIndexes.length-1]]); + } else { + this.commands[index].setRight(this.commands[index+1]); + } + + this.commands[index].setLeft(undefined); + } else if (this.commands[index].command === "end") { + endIndexes.push(index); + } else if (this.commands[index].command === "if") { + endIndexes.pop(); + } + } + inverseControlFlowSwitcher(index, endBlockIndexes) { + let lastEndBlockIndex; switch(this.commands[index].command) { case "if": + case "elseIf": + lastEndBlockIndex = endBlockIndexes.pop(); + if (this.commands[lastEndBlockIndex].command === "elseIf") { + this.commands[index].setRight(this.commands[index+1]); + this.commands[index].setLeft(this.commands[lastEndBlockIndex]); + } else { + this.commands[index].setRight(this.commands[index+1]); + this.commands[index].setLeft(this.commands[lastEndBlockIndex + 1]); + } + if (this.commands[index].command === "elseIf") { + endBlockIndexes.push(index); + } + break; case "while": this.commands[index].setRight(this.commands[index+1]); this.commands[index].setLeft(this.commands[endBlockIndexes.pop() + 1]); @@ -69,11 +98,11 @@ export default class PlaybackTree{ } controlFlowSwitcher(index, blocks) { - if (["if", "else", "while"].includes(this.commands[index].command)) { + if (["if", "else", "while", "elseIf"].includes(this.commands[index].command)) { blocks.push(this.commands[index]); } else if (this.commands[index].command === "end") { let lastBlock = blocks.pop(); - if(["if", "else"].includes(lastBlock.command)) { + if(["if", "else", "elseIf"].includes(lastBlock.command)) { this.commands[index].setRight(this.commands[index+1]); } else if (lastBlock.command === "while") { this.commands[index].setRight(lastBlock); @@ -93,6 +122,7 @@ export default class PlaybackTree{ switch(command) { case "if": case "while": + case "elseIf": case "else": return true; default: @@ -105,6 +135,7 @@ export default class PlaybackTree{ switch(command) { case "end": case "else": + case "elseIf": return true; default: return false; @@ -112,7 +143,8 @@ export default class PlaybackTree{ } static processCommands(commandsArray) { - new PlaybackTree(commandsArray); + let a = new PlaybackTree(commandsArray); + a.maintenance(); } // TODO: maintenance function: remove when possible diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index f86ba17550..17da11518d 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -129,8 +129,9 @@ export const Commands = Object.freeze({ editContent: "edit content", else: "else", end: "end", - endDo: "endDo", + repeatIf: "repeatIf", if: "if", + elseIf: "elseIf", mouseDownAt: "mouse down at", mouseMoveAt: "mouse move at", mouseOut: "mouse out", From 30a4378b21323b8e52f65e89b42c52ff293dac6b Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 01:42:23 +0300 Subject: [PATCH 11/23] do-repeatif commands --- .../selenium-ide/src/content/commands-api.js | 2 +- .../selenium-ide/src/content/selenium-api.js | 8 ++++++++ .../src/neo/IO/SideeX/playbackTree.js | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index da8b01fbae..9617153234 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -115,7 +115,7 @@ function doCommands(request, sender, sendResponse) { } function isConditinal(commandName) { - return ["if", "while", "elseIf"].includes(commandName); + return ["if", "while", "elseIf", "repeatIf"].includes(commandName); } function executeConditional(request, sendResponse) { diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index 4e1f8d5680..6cb7b3fd20 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -440,6 +440,10 @@ Selenium.prototype.doElseIf = function(string) { return eval(string); }; +Selenium.prototype.doRepeatIf = function(string) { + return eval(string); +}; + Selenium.prototype.doElse = function() { return true; }; @@ -448,6 +452,10 @@ Selenium.prototype.doEnd = function() { return true; }; +Selenium.prototype.doDo = function() { + return true; +}; + Selenium.prototype.doVerifyElementNotPresent = function(locator) { try { this.browserbot.findElement(locator); diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index e269cc0066..136faa0c8e 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -29,6 +29,7 @@ export default class PlaybackTree{ let endIndexes = []; let endBlockIndexes = []; let blocks = []; + let doBlockIndexes = []; for(let i = this.commands.length-1; i>=0; i--) { this.inverseExtCommandSwitcher(i, endIndexes); if (this.isControlFlowFunction(this.commands[i].command)) { @@ -38,7 +39,7 @@ export default class PlaybackTree{ for(let i = 0; i < this.commands.length; i++) { if (this.isControlFlowFunction(this.commands[i].command)) { - this.controlFlowSwitcher(i, blocks); + this.controlFlowSwitcher(i, blocks, doBlockIndexes); } } @@ -47,8 +48,8 @@ export default class PlaybackTree{ inverseExtCommandSwitcher(index, endIndexes) { if (!this.isControlFlowFunction(this.commands[index].command)) { - // commands preceding 'else', 'elseIf' or 'end' will point to appropriate end in the right - if (endIndexes.length > 0 && this.isBlockEnd(this.commands[index+1].command)) { + // commands preceding 'else', 'elseIf' will point to appropriate end in the right + if (endIndexes.length > 0 && ["else", "elseIf"].includes(this.commands[index+1].command)) { this.commands[index].setRight(this.commands[endIndexes[endIndexes.length-1]]); } else { this.commands[index].setRight(this.commands[index+1]); @@ -97,7 +98,7 @@ export default class PlaybackTree{ } } - controlFlowSwitcher(index, blocks) { + controlFlowSwitcher(index, blocks, doBlockIndexes) { if (["if", "else", "while", "elseIf"].includes(this.commands[index].command)) { blocks.push(this.commands[index]); } else if (this.commands[index].command === "end") { @@ -107,6 +108,13 @@ export default class PlaybackTree{ } else if (lastBlock.command === "while") { this.commands[index].setRight(lastBlock); } + } else if (this.commands[index].command === "do") { + this.commands[index].setLeft(undefined); + this.commands[index].setRight(this.commands[index+1]); + doBlockIndexes.push(index); + } else if (this.commands[index].command === "repeatIf") { + this.commands[index].setLeft(this.commands[index+1]); + this.commands[index].setRight(this.commands[doBlockIndexes.pop()]); } else { window.addLog(`Unknown control flow operator "${this.commands[index].command}"`); } @@ -124,6 +132,7 @@ export default class PlaybackTree{ case "while": case "elseIf": case "else": + case "do": return true; default: return false; @@ -136,6 +145,7 @@ export default class PlaybackTree{ case "end": case "else": case "elseIf": + case "repeatIf": return true; default: return false; From db788d6a1917e63cf186dc25123f0a935744f2d9 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 02:59:04 +0300 Subject: [PATCH 12/23] 'times' command from playback tree perspective --- packages/selenium-ide/src/content/commands-api.js | 2 +- packages/selenium-ide/src/content/selenium-api.js | 5 +++++ .../selenium-ide/src/neo/IO/SideeX/playbackTree.js | 10 ++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 9617153234..896e7af4b8 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -115,7 +115,7 @@ function doCommands(request, sender, sendResponse) { } function isConditinal(commandName) { - return ["if", "while", "elseIf", "repeatIf"].includes(commandName); + return ["if", "while", "elseIf", "repeatIf", "times"].includes(commandName); } function executeConditional(request, sendResponse) { diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index 6cb7b3fd20..cff4e6766c 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -436,6 +436,11 @@ Selenium.prototype.doWhile = function(string) { return eval(string); }; +Selenium.prototype.doTimes = function(counter) { + // return to it when 'eval' and stored variables properly figured out + return true; +}; + Selenium.prototype.doElseIf = function(string) { return eval(string); }; diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 136faa0c8e..06fddb36f1 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -80,6 +80,7 @@ export default class PlaybackTree{ endBlockIndexes.push(index); } break; + case "times": case "while": this.commands[index].setRight(this.commands[index+1]); this.commands[index].setLeft(this.commands[endBlockIndexes.pop() + 1]); @@ -99,13 +100,17 @@ export default class PlaybackTree{ } controlFlowSwitcher(index, blocks, doBlockIndexes) { - if (["if", "else", "while", "elseIf"].includes(this.commands[index].command)) { + if (["if", "else", "while", "elseIf", "times"].includes(this.commands[index].command)) { + if (["else", "elseIf"].includes(this.commands[index].command)) { + // treat if-elseif-else constructions as closed blocks + blocks.pop(); + } blocks.push(this.commands[index]); } else if (this.commands[index].command === "end") { let lastBlock = blocks.pop(); if(["if", "else", "elseIf"].includes(lastBlock.command)) { this.commands[index].setRight(this.commands[index+1]); - } else if (lastBlock.command === "while") { + } else if (lastBlock.command === "while" || lastBlock.command === "times" ) { this.commands[index].setRight(lastBlock); } } else if (this.commands[index].command === "do") { @@ -130,6 +135,7 @@ export default class PlaybackTree{ switch(command) { case "if": case "while": + case "times": case "elseIf": case "else": case "do": From 570d7deb4bb065e11aa7394bd2fbad2c037f5951 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 03:12:31 +0300 Subject: [PATCH 13/23] playbackTree.js adjustments and continue|break dummies --- .../selenium-ide/src/content/selenium-api.js | 8 ++++ .../src/neo/IO/SideeX/playbackTree.js | 37 +++---------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index cff4e6766c..173c70f28c 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -461,6 +461,14 @@ Selenium.prototype.doDo = function() { return true; }; +Selenium.prototype.doContinue = function() { + return true; +}; + +Selenium.prototype.doBreak = function() { + return true; +}; + Selenium.prototype.doVerifyElementNotPresent = function(locator) { try { this.browserbot.findElement(locator); diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 06fddb36f1..83bcd2f7fe 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -125,33 +125,18 @@ export default class PlaybackTree{ } } - // TODO: avoid duplicates with TestTable isControlFlowFunction(command) { - return this.isBlock(command) || this.isBlockEnd(command); - } - - // TODO: do, times - isBlock(command) { switch(command) { case "if": - case "while": - case "times": case "elseIf": case "else": + case "while": + case "times": + case "repeatIf": case "do": - return true; - default: - return false; - } - } - - // TODO: endDo - isBlockEnd(command) { - switch(command) { case "end": - case "else": - case "elseIf": - case "repeatIf": + case "continue": + case "break": return true; default: return false; @@ -165,7 +150,6 @@ export default class PlaybackTree{ // TODO: maintenance function: remove when possible maintenance() { - // runCommand(this.executionNodes[0]); window.addLog("maintenance"); this.executionNodes.forEach((node) => { @@ -182,14 +166,5 @@ export default class PlaybackTree{ } window.addLog(`----------------------`); }); - - // function runCommand(command) { - // window.addLog() - // console.log(command.command); - // if (command.right) { - // runCommand(command.right); - // } - // } } - -} \ No newline at end of file +} From 5f50adec1bcd658616ea17305caf594e3e45468d Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 03:23:47 +0300 Subject: [PATCH 14/23] slightly more readable playbackTree.js --- .../src/neo/IO/SideeX/playbackTree.js | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index 83bcd2f7fe..d4232fcc22 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -47,81 +47,81 @@ export default class PlaybackTree{ } inverseExtCommandSwitcher(index, endIndexes) { - if (!this.isControlFlowFunction(this.commands[index].command)) { + if (!this.isControlFlowFunction(this.com(index).command)) { // commands preceding 'else', 'elseIf' will point to appropriate end in the right - if (endIndexes.length > 0 && ["else", "elseIf"].includes(this.commands[index+1].command)) { - this.commands[index].setRight(this.commands[endIndexes[endIndexes.length-1]]); + if (endIndexes.length > 0 && ["else", "elseIf"].includes(this.com(index+1).command)) { + this.com(index).setRight(this.com(endIndexes[endIndexes.length-1])); } else { - this.commands[index].setRight(this.commands[index+1]); + this.com(index).setRight(this.com(index+1)); } - this.commands[index].setLeft(undefined); - } else if (this.commands[index].command === "end") { + this.com(index).setLeft(undefined); + } else if (this.com(index).command === "end") { endIndexes.push(index); - } else if (this.commands[index].command === "if") { + } else if (this.com(index).command === "if") { endIndexes.pop(); } } inverseControlFlowSwitcher(index, endBlockIndexes) { let lastEndBlockIndex; - switch(this.commands[index].command) { + switch(this.com(index).command) { case "if": case "elseIf": lastEndBlockIndex = endBlockIndexes.pop(); if (this.commands[lastEndBlockIndex].command === "elseIf") { - this.commands[index].setRight(this.commands[index+1]); - this.commands[index].setLeft(this.commands[lastEndBlockIndex]); + this.com(index).setRight(this.com(index+1)); + this.com(index).setLeft(this.com(lastEndBlockIndex)); } else { - this.commands[index].setRight(this.commands[index+1]); - this.commands[index].setLeft(this.commands[lastEndBlockIndex + 1]); + this.com(index).setRight(this.com(index+1)); + this.com(index).setLeft(this.com(lastEndBlockIndex + 1)); } - if (this.commands[index].command === "elseIf") { + if (this.com(index).command === "elseIf") { endBlockIndexes.push(index); } break; case "times": case "while": - this.commands[index].setRight(this.commands[index+1]); - this.commands[index].setLeft(this.commands[endBlockIndexes.pop() + 1]); + this.com(index).setRight(this.com(index+1)); + this.com(index).setLeft(this.com(endBlockIndexes.pop() + 1)); break; case "else": - this.commands[index].setRight(this.commands[endBlockIndexes.pop() + 1]); - this.commands[index].setLeft(undefined); + this.com(index).setRight(this.com(endBlockIndexes.pop() + 1)); + this.com(index).setLeft(undefined); endBlockIndexes.push(index); break; case "end": - this.commands[index].setLeft(undefined); + this.com(index).setLeft(undefined); endBlockIndexes.push(index); break; default: - window.addLog(`Unknown control flow operator "${this.commands[index].command}"`); + window.addLog(`Unknown control flow operator "${this.com(index).command}"`); } } controlFlowSwitcher(index, blocks, doBlockIndexes) { - if (["if", "else", "while", "elseIf", "times"].includes(this.commands[index].command)) { - if (["else", "elseIf"].includes(this.commands[index].command)) { + if (["if", "else", "while", "elseIf", "times"].includes(this.com(index).command)) { + if (["else", "elseIf"].includes(this.com(index).command)) { // treat if-elseif-else constructions as closed blocks blocks.pop(); } - blocks.push(this.commands[index]); - } else if (this.commands[index].command === "end") { + blocks.push(this.com(index)); + } else if (this.com(index).command === "end") { let lastBlock = blocks.pop(); if(["if", "else", "elseIf"].includes(lastBlock.command)) { - this.commands[index].setRight(this.commands[index+1]); + this.com(index).setRight(this.com(index+1)); } else if (lastBlock.command === "while" || lastBlock.command === "times" ) { - this.commands[index].setRight(lastBlock); + this.com(index).setRight(lastBlock); } - } else if (this.commands[index].command === "do") { - this.commands[index].setLeft(undefined); - this.commands[index].setRight(this.commands[index+1]); + } else if (this.com(index).command === "do") { + this.com(index).setLeft(undefined); + this.com(index).setRight(this.com(index+1)); doBlockIndexes.push(index); - } else if (this.commands[index].command === "repeatIf") { - this.commands[index].setLeft(this.commands[index+1]); - this.commands[index].setRight(this.commands[doBlockIndexes.pop()]); + } else if (this.com(index).command === "repeatIf") { + this.com(index).setLeft(this.com(index+1)); + this.com(index).setRight(this.com(doBlockIndexes.pop())); } else { - window.addLog(`Unknown control flow operator "${this.commands[index].command}"`); + window.addLog(`Unknown control flow operator "${this.com(index).command}"`); } } @@ -143,6 +143,10 @@ export default class PlaybackTree{ } } + com(index) { + return this.commands[index]; + } + static processCommands(commandsArray) { let a = new PlaybackTree(commandsArray); a.maintenance(); From cadf4534852cd0ddd036b1097c0cf3c9e1940846 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 03:28:51 +0300 Subject: [PATCH 15/23] Adjustments for indents --- packages/selenium-ide/src/neo/components/TestRow/index.jsx | 2 +- packages/selenium-ide/src/neo/components/TestTable/index.jsx | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/components/TestRow/index.jsx b/packages/selenium-ide/src/neo/components/TestRow/index.jsx index a3ff29a7c6..2efc2f064e 100644 --- a/packages/selenium-ide/src/neo/components/TestRow/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestRow/index.jsx @@ -201,7 +201,7 @@ class TestRow extends React.Component { //setting component of context menu. this.props.setContextMenu(listMenu); - const index = this.props.index >= 0 ? {this.props.index + 1}. : null; + const index = this.props.index >= 0 ? {this.props.index + 1}. : null; const rendered = {return(this.node = node || this.node);}} className={classNames(this.props.className, {"selected": this.props.selected}, {"break-point": this.props.isBreakpoint}, {"dragging": this.props.dragInProgress})} diff --git a/packages/selenium-ide/src/neo/components/TestTable/index.jsx b/packages/selenium-ide/src/neo/components/TestTable/index.jsx index 8762cfcd64..1fc51d9952 100644 --- a/packages/selenium-ide/src/neo/components/TestTable/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestTable/index.jsx @@ -116,6 +116,7 @@ function isBlock(command) { case "do": case "while": case "times": + case "elseIf": case "else": return true; default: @@ -126,7 +127,8 @@ function isBlock(command) { function isBlockEnd(command) { switch(command) { case "end": - case "endDo": + case "repeatIf": + case "elseIf": case "else": return true; default: From f18b295bbcd4113de5c583b23e4280ec4082dc4d Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 03:36:15 +0300 Subject: [PATCH 16/23] Close end blocks in 'inverseExtCommandSwitcher' --- packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index d4232fcc22..dd38fcdec3 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -58,7 +58,7 @@ export default class PlaybackTree{ this.com(index).setLeft(undefined); } else if (this.com(index).command === "end") { endIndexes.push(index); - } else if (this.com(index).command === "if") { + } else if (["if", "times", "while"].includes(this.com(index).command)) { endIndexes.pop(); } } From 3ac4ba25d35963b314ef1556ad4406bd8fdd50a5 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 05:18:57 +0300 Subject: [PATCH 17/23] 'continue' command --- .../src/neo/IO/SideeX/playbackTree.js | 136 ++++++++++++------ 1 file changed, 96 insertions(+), 40 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index dd38fcdec3..a97b52b1b5 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -30,6 +30,12 @@ export default class PlaybackTree{ let endBlockIndexes = []; let blocks = []; let doBlockIndexes = []; + + // for continue + let whileTimesIndexes = []; + let doIndexes = []; + let ifIndexes = []; + let nextRepeatIfContinueIndex = {index: undefined}; for(let i = this.commands.length-1; i>=0; i--) { this.inverseExtCommandSwitcher(i, endIndexes); if (this.isControlFlowFunction(this.commands[i].command)) { @@ -40,88 +46,138 @@ export default class PlaybackTree{ for(let i = 0; i < this.commands.length; i++) { if (this.isControlFlowFunction(this.commands[i].command)) { this.controlFlowSwitcher(i, blocks, doBlockIndexes); + this.continueProcessor(i, whileTimesIndexes, doIndexes, ifIndexes, nextRepeatIfContinueIndex); } } this.executionNodes = this.commands; } - inverseExtCommandSwitcher(index, endIndexes) { - if (!this.isControlFlowFunction(this.com(index).command)) { + continueProcessor(i, loopIndexes, doIndexes, ifIndexes, nextRepeatIfContinueIndex) { + let loops = ["times", "while"]; + if (loops.includes(this.com(i).command)) { + loopIndexes.push(i); + } else if (this.com(i).command === "do") { + doIndexes.push(i); + } else if (this.com(i).command === "if") { + ifIndexes.push(i); + } else if (this.com(i).command === "continue") { + if (doIndexes.length === 0) { + this.com(i).setRight(this.com(loopIndexes[loopIndexes.length-1])); + this.com(i).setLeft(undefined); + } + if (loopIndexes[loopIndexes.length-1] > doIndexes[doIndexes.length-1]) { + this.com(i).setRight(this.com(loopIndexes[loopIndexes.length-1])); + this.com(i).setLeft(undefined); + } else { + nextRepeatIfContinueIndex.index = i; + } + } else if (this.com(i).command === "end") { + if (loopIndexes[loopIndexes-1] > ifIndexes[ifIndexes-1] ) { + loopIndexes.pop(); + } else { + ifIndexes.pop(); + } + } else if (this.com(i).command === "repeatIf") { + doIndexes.pop(); + if(nextRepeatIfContinueIndex.index) { + this.com(nextRepeatIfContinueIndex.index).setRight(this.com(i)); + this.com(nextRepeatIfContinueIndex.index).setLeft(undefined); + } + } + } + + // continueProcessor(i, loopIndexes, ifIndexes) { + // let loops = ["times", "while"]; + // if (loops.includes(this.com(i).command)) { + // loopIndexes.push(i); + // } else if (this.com(i).command === "if") { + // ifIndexes.push(i); + // } else if (this.com(i).command === "continue") { + // this.com(i).setRight(loopIndexes[loopIndexes.length-1]); + // this.com(i).setLeft(undefined); + // } else if (this.com(i).command === "end") { + // loopIndexes.pop(); + // ifIndexes.pop(); + // } + // } + + inverseExtCommandSwitcher(i, endIndexes) { + if (!this.isControlFlowFunction(this.com(i).command)) { // commands preceding 'else', 'elseIf' will point to appropriate end in the right - if (endIndexes.length > 0 && ["else", "elseIf"].includes(this.com(index+1).command)) { - this.com(index).setRight(this.com(endIndexes[endIndexes.length-1])); + if (endIndexes.length > 0 && ["else", "elseIf"].includes(this.com(i+1).command)) { + this.com(i).setRight(this.com(endIndexes[endIndexes.length-1])); } else { - this.com(index).setRight(this.com(index+1)); + this.com(i).setRight(this.com(i+1)); } - this.com(index).setLeft(undefined); - } else if (this.com(index).command === "end") { - endIndexes.push(index); - } else if (["if", "times", "while"].includes(this.com(index).command)) { + this.com(i).setLeft(undefined); + } else if (this.com(i).command === "end") { + endIndexes.push(i); + } else if (["if", "times", "while"].includes(this.com(i).command)) { endIndexes.pop(); } } - inverseControlFlowSwitcher(index, endBlockIndexes) { + inverseControlFlowSwitcher(i, endBlockIndexes) { let lastEndBlockIndex; - switch(this.com(index).command) { + switch(this.com(i).command) { case "if": case "elseIf": lastEndBlockIndex = endBlockIndexes.pop(); if (this.commands[lastEndBlockIndex].command === "elseIf") { - this.com(index).setRight(this.com(index+1)); - this.com(index).setLeft(this.com(lastEndBlockIndex)); + this.com(i).setRight(this.com(i+1)); + this.com(i).setLeft(this.com(lastEndBlockIndex)); } else { - this.com(index).setRight(this.com(index+1)); - this.com(index).setLeft(this.com(lastEndBlockIndex + 1)); + this.com(i).setRight(this.com(i+1)); + this.com(i).setLeft(this.com(lastEndBlockIndex + 1)); } - if (this.com(index).command === "elseIf") { - endBlockIndexes.push(index); + if (this.com(i).command === "elseIf") { + endBlockIndexes.push(i); } break; case "times": case "while": - this.com(index).setRight(this.com(index+1)); - this.com(index).setLeft(this.com(endBlockIndexes.pop() + 1)); + this.com(i).setRight(this.com(i+1)); + this.com(i).setLeft(this.com(endBlockIndexes.pop() + 1)); break; case "else": - this.com(index).setRight(this.com(endBlockIndexes.pop() + 1)); - this.com(index).setLeft(undefined); - endBlockIndexes.push(index); + this.com(i).setRight(this.com(endBlockIndexes.pop() + 1)); + this.com(i).setLeft(undefined); + endBlockIndexes.push(i); break; case "end": - this.com(index).setLeft(undefined); - endBlockIndexes.push(index); + this.com(i).setLeft(undefined); + endBlockIndexes.push(i); break; default: - window.addLog(`Unknown control flow operator "${this.com(index).command}"`); + window.addLog(`Unknown control flow operator "${this.com(i).command}"`); } } - controlFlowSwitcher(index, blocks, doBlockIndexes) { - if (["if", "else", "while", "elseIf", "times"].includes(this.com(index).command)) { - if (["else", "elseIf"].includes(this.com(index).command)) { + controlFlowSwitcher(i, blocks, doBlockIndexes) { + if (["if", "else", "while", "elseIf", "times"].includes(this.com(i).command)) { + if (["else", "elseIf"].includes(this.com(i).command)) { // treat if-elseif-else constructions as closed blocks blocks.pop(); } - blocks.push(this.com(index)); - } else if (this.com(index).command === "end") { + blocks.push(this.com(i)); + } else if (this.com(i).command === "end") { let lastBlock = blocks.pop(); if(["if", "else", "elseIf"].includes(lastBlock.command)) { - this.com(index).setRight(this.com(index+1)); + this.com(i).setRight(this.com(i+1)); } else if (lastBlock.command === "while" || lastBlock.command === "times" ) { - this.com(index).setRight(lastBlock); + this.com(i).setRight(lastBlock); } - } else if (this.com(index).command === "do") { - this.com(index).setLeft(undefined); - this.com(index).setRight(this.com(index+1)); - doBlockIndexes.push(index); - } else if (this.com(index).command === "repeatIf") { - this.com(index).setLeft(this.com(index+1)); - this.com(index).setRight(this.com(doBlockIndexes.pop())); + } else if (this.com(i).command === "do") { + this.com(i).setLeft(undefined); + this.com(i).setRight(this.com(i+1)); + doBlockIndexes.push(i); + } else if (this.com(i).command === "repeatIf") { + this.com(i).setLeft(this.com(i+1)); + this.com(i).setRight(this.com(doBlockIndexes.pop())); } else { - window.addLog(`Unknown control flow operator "${this.com(index).command}"`); + window.addLog(`Unknown control flow operator "${this.com(i).command}"`); } } From b18fab30ae418192eefaac3e08112d82235c0733 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 30 May 2018 05:45:03 +0300 Subject: [PATCH 18/23] Continue warning --- .../src/neo/IO/SideeX/playbackTree.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js index a97b52b1b5..30029f54c0 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playbackTree.js @@ -53,6 +53,7 @@ export default class PlaybackTree{ this.executionNodes = this.commands; } + // won't work with embedded loops following continue. TODO: redo that continueProcessor(i, loopIndexes, doIndexes, ifIndexes, nextRepeatIfContinueIndex) { let loops = ["times", "while"]; if (loops.includes(this.com(i).command)) { @@ -87,21 +88,6 @@ export default class PlaybackTree{ } } - // continueProcessor(i, loopIndexes, ifIndexes) { - // let loops = ["times", "while"]; - // if (loops.includes(this.com(i).command)) { - // loopIndexes.push(i); - // } else if (this.com(i).command === "if") { - // ifIndexes.push(i); - // } else if (this.com(i).command === "continue") { - // this.com(i).setRight(loopIndexes[loopIndexes.length-1]); - // this.com(i).setLeft(undefined); - // } else if (this.com(i).command === "end") { - // loopIndexes.pop(); - // ifIndexes.pop(); - // } - // } - inverseExtCommandSwitcher(i, endIndexes) { if (!this.isControlFlowFunction(this.com(i).command)) { // commands preceding 'else', 'elseIf' will point to appropriate end in the right From 5905bb29f77e1d256af220a502b17d046f0ce955 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Mon, 4 Jun 2018 18:21:04 +0300 Subject: [PATCH 19/23] Suite Validator added --- .../src/neo/IO/SideeX/suiteValidator.js | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js diff --git a/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js new file mode 100644 index 0000000000..3ad24fd882 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js @@ -0,0 +1,128 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + + +// For now it iterates through commands array twice. Not sure how to avoid that for now + +export default class SuiteValidator{ + constructor(commandsArray) { + this.blocksStack = []; + this.commandNamesArray = commandsArray.map((command) => { + return command.command; + }); + this.error = {}; + this.process(); + } + + process() { + for(let i = 0; i < this.commandNamesArray.length; i++) { + const commandName = this.commandNamesArray[i]; + if (!this.isBlockEnder(commandName)) { + this.processBlock(commandName, i); + } else { + this.processBlockEnd(commandName, i); + } + if(this.isIntermediateValid() !== true) break; + } + } + + processBlock(command, index) { + if(["else", "elseIf"].includes(command)) { + if (this.isIfElseOpen()) { + this.pushToStack(command); + } else { + this.error = {index: index, error: "Incorrect if-elseIf-else block"}; + } + } else if (["continue", "break"].includes(command)) { + if (!this.isLoopOpen()) { + this.error = {index: index, error: "'continue' and 'break' can only be called inside loop"}; + } + } else { + this.pushToStack(command); + } + } + + isIfElseOpen() { + const lastIfCommandIndex = this.blocksStack.lastIndexOf("if"); + const lastElseCommandIndex = this.blocksStack.lastIndexOf("else"); + const ifPresent = lastIfCommandIndex > -1; + return ifPresent && lastElseCommandIndex < lastIfCommandIndex; + } + + isLoopOpen() { + ["times", "do", "while"].forEach((loop) => { + if (this.blocksStack.includes(loop)) { + return true; + } + }); + return false; + } + + processBlockEnd(command, i) { + if(this.blocksStack.length === 0) { + this.error = {index: i, error: "Error with the syntax"}; + } + if(command === "end") { + this.closeEndBlock(i); + } else { + this.closeRepeatIfBlock(i); + } + } + + closeEndBlock(index) { + if(["if", "elseIf"].includes(this.blocksStack[this.blocksStack.length-1])) { + // remove all of the if-elseIf-else commands from the stack + const lastIfCommandIndex = this.blocksStack.lastIndexOf("if"); + this.blocksStack.splice(lastIfCommandIndex, this.blocksStack.length-1-lastIfCommandIndex); + } else if (this.blocksStack[this.blocksStack.length-1] === "do") { + this.error = {index: index, error: "'do' must be enclosed with 'repeatIf'"}; + } else { + this.blocksStack.pop(); + } + } + + closeRepeatIfBlock(index) { + if (this.blocksStack[this.blocksStack.length-1] === "do") { + this.blocksStack.pop(); + } else { + this.error = {index: index, error: "'do' must be enclosed with 'repeatIf'"}; + } + } + + pushToStack(command) { + this.blocksStack.push(command); + } + + isIntermediateValid() { + if(this.error["index"]) { + return this.error; + } else { + return true; + } + } + + findLastCommand(commandName) { + return this.commandNamesArray.lastIndexOf(commandName); + } + + isValid() { + if(this.blocksStack.length > 0) { + this.error = {index: this.findLastCommand(this.blocksStack[this.blocksStack.length-1]), error: "Error with the syntax"}; + } + } +} + From 3d92c0e693dbf594c5958477073f739fd677fd09 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Mon, 4 Jun 2018 23:16:43 +0300 Subject: [PATCH 20/23] Fix issue with validating if-elseif-else blocks. Additional flexibility for validation. --- .../src/neo/IO/SideeX/suiteValidator.js | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js index 3ad24fd882..ae1495923d 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js @@ -15,15 +15,10 @@ // specific language governing permissions and limitations // under the License. - -// For now it iterates through commands array twice. Not sure how to avoid that for now - export default class SuiteValidator{ - constructor(commandsArray) { + constructor(commandNamesArray) { this.blocksStack = []; - this.commandNamesArray = commandsArray.map((command) => { - return command.command; - }); + this.commandNamesArray = commandNamesArray; this.error = {}; this.process(); } @@ -31,12 +26,17 @@ export default class SuiteValidator{ process() { for(let i = 0; i < this.commandNamesArray.length; i++) { const commandName = this.commandNamesArray[i]; - if (!this.isBlockEnder(commandName)) { + if(!this.isControlFlowFunction(commandName)) { + continue; + } + if (!["end", "repeatIf"].includes(commandName)) { this.processBlock(commandName, i); } else { this.processBlockEnd(commandName, i); } - if(this.isIntermediateValid() !== true) break; + if(this.isIntermediateValid() !== true) { + break; + } } } @@ -84,10 +84,10 @@ export default class SuiteValidator{ } closeEndBlock(index) { - if(["if", "elseIf"].includes(this.blocksStack[this.blocksStack.length-1])) { + if(["else", "elseIf"].includes(this.blocksStack[this.blocksStack.length-1])) { // remove all of the if-elseIf-else commands from the stack const lastIfCommandIndex = this.blocksStack.lastIndexOf("if"); - this.blocksStack.splice(lastIfCommandIndex, this.blocksStack.length-1-lastIfCommandIndex); + this.blocksStack.splice(lastIfCommandIndex, this.blocksStack.length-lastIfCommandIndex); } else if (this.blocksStack[this.blocksStack.length-1] === "do") { this.error = {index: index, error: "'do' must be enclosed with 'repeatIf'"}; } else { @@ -123,6 +123,18 @@ export default class SuiteValidator{ if(this.blocksStack.length > 0) { this.error = {index: this.findLastCommand(this.blocksStack[this.blocksStack.length-1]), error: "Error with the syntax"}; } + return this.isIntermediateValid(); + } + + isControlFlowFunction(command) { + return ["if", "elseIf", "else", "while", + "times", "repeatIf", "do", "end", + "continue", "break"].includes(command); + } + + static validateCommands(commandsArray) { + let validator = new SuiteValidator(commandsArray); + return validator.isValid(); } } From 157ea3fd14cf99a8669747e8581a86a97fa07f55 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Tue, 5 Jun 2018 02:23:30 +0300 Subject: [PATCH 21/23] Fix 'continue' and 'break' validation. Add suite validator specs --- .../src/neo/IO/SideeX/suiteValidator.js | 15 +- .../neo/__test__/IO/suiteValidator.spec.js | 153 ++++++++++++++++++ 2 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 packages/selenium-ide/src/neo/__test__/IO/suiteValidator.spec.js diff --git a/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js index ae1495923d..11d6ee85da 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/suiteValidator.js @@ -26,17 +26,13 @@ export default class SuiteValidator{ process() { for(let i = 0; i < this.commandNamesArray.length; i++) { const commandName = this.commandNamesArray[i]; - if(!this.isControlFlowFunction(commandName)) { - continue; - } + if(!this.isControlFlowFunction(commandName)) continue; if (!["end", "repeatIf"].includes(commandName)) { this.processBlock(commandName, i); } else { this.processBlockEnd(commandName, i); } - if(this.isIntermediateValid() !== true) { - break; - } + if(this.isIntermediateValid() !== true) break; } } @@ -64,12 +60,13 @@ export default class SuiteValidator{ } isLoopOpen() { + let loopIsPresent = false; ["times", "do", "while"].forEach((loop) => { if (this.blocksStack.includes(loop)) { - return true; + loopIsPresent = true; } }); - return false; + return loopIsPresent; } processBlockEnd(command, i) { @@ -108,7 +105,7 @@ export default class SuiteValidator{ } isIntermediateValid() { - if(this.error["index"]) { + if(this.error["error"]) { return this.error; } else { return true; diff --git a/packages/selenium-ide/src/neo/__test__/IO/suiteValidator.spec.js b/packages/selenium-ide/src/neo/__test__/IO/suiteValidator.spec.js new file mode 100644 index 0000000000..1f2cb69d23 --- /dev/null +++ b/packages/selenium-ide/src/neo/__test__/IO/suiteValidator.spec.js @@ -0,0 +1,153 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import SuiteValidator from "../../IO/SideeX/suiteValidator"; + +describe("SuiteValidator if blocks", () => { + it("should treat as valid if-elseif-else-end block", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "else", "end"])).toBe(true); + }); + + it("should treat as valid if-elseif-else-end block with multiple elseIf", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "elseIf", "elseIf", "else", "end"])).toBe(true); + }); + + it("should treat as valid if-elseif-end block", () => { + expect(SuiteValidator.validateCommands(["if", "else", "end"])).toBe(true); + }); + + it("should treat as valid if-end block", () => { + expect(SuiteValidator.validateCommands(["if", "end"])).toBe(true); + }); + + it("should treat as valid if-elseif-end block", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "elseIf", "elseIf", "end"])).toBe(true); + }); + + it("should treat as valid if-end block with embedded loops", () => { + expect(SuiteValidator.validateCommands(["if", "while", "end", "times", "end", "end"])).toBe(true); + }); + + it("should treat as valid if-elseif-end block with embedded do-repeatIf loop", () => { + expect(SuiteValidator.validateCommands(["if", "else", "do", "blob", "repeatIf", "end"])).toBe(true); + }); + + it("should treat as valid if-elseif-end block with embedded loop", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "while", "blob", "end", "elseIf", "elseIf", "end"])).toBe(true); + }); + + it("should treat as valid while-end block", () => { + expect(SuiteValidator.validateCommands(["while", "blob", "end"])).toBe(true); + }); + + it("should treat as valid while-end block with embedded if-else-end", () => { + expect(SuiteValidator.validateCommands(["while", "if", "blob", "else", "end", "end"])).toBe(true); + }); + + it("should treat as valid while-end block with embedded times-end ", () => { + expect(SuiteValidator.validateCommands(["while", "blob", "times", "blob", "end", "end"])).toBe(true); + }); + + it("should treat as valid do-repeatIf block", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "repeatIf"])).toBe(true); + }); + + it("should treat as valid do-repeatIf block with embedded if-else-end", () => { + expect(SuiteValidator.validateCommands(["do", "if", "blob", "else", "end", "repeatIf"])).toBe(true); + }); + + it("should treat as valid do-repeatIf block with embedded times-end ", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "times", "blob", "end", "repeatIf"])).toBe(true); + }); + + it("should treat as valid do-repeatIf block with embedded continue", () => { + expect(SuiteValidator.validateCommands(["do", "if", "continue", "end", "repeatIf"])).toBe(true); + }); + + it("should treat as valid do-repeatIf block with embedded continue with embedded times-end with embedded continue", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "times", "blob", "continue", "end", "continue", "repeatIf"])).toBe(true); + }); + + it("should treat as valid while-end block with embedded continue", () => { + expect(SuiteValidator.validateCommands(["while", "if", "blob", "continue", "else", "continue", "end", "end"])).toBe(true); + }); + + it("should treat as valid while-end block with embedded break", () => { + expect(SuiteValidator.validateCommands(["while", "if", "blob", "break", "else", "continue", "end", "end"])).toBe(true); + }); +}); + +describe("SuiteValidator failure", () => { + it("should fail if-else-elseIf-end block", () => { + expect(SuiteValidator.validateCommands(["if", "else", "elseIf", "end"])).not.toBe(true); + }); + + it("should fail if-else block", () => { + expect(SuiteValidator.validateCommands(["if", "else"])).not.toBe(true); + }); + + it("should fail if-elseif-else-end block with unclosed loop", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "else", "while" , "end"])).not.toBe(true); + }); + + it("should fail if-elseif-else-end block with unclosed do loop", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "else", "do" , "end"])).not.toBe(true); + }); + + it("should fail if-elseif-else-end block with unclosed repeatIf loop", () => { + expect(SuiteValidator.validateCommands(["if", "elseIf", "else", "repeatIf" , "end"])).not.toBe(true); + }); + + it("should fail while-end block with embedded if-else", () => { + expect(SuiteValidator.validateCommands(["while", "if", "blob", "else", "end"])).not.toBe(true); + }); + + it("should fail while-end block with embedded unclosed times", () => { + expect(SuiteValidator.validateCommands(["while", "blob", "times", "blob", "end"])).not.toBe(true); + }); + + it("should fail while-end block with embedded unclosed repeatIf", () => { + expect(SuiteValidator.validateCommands(["while", "blob", "repeatIf", "blob", "end"])).not.toBe(true); + }); + + it("should fail do-repeatIf block", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "end"])).not.toBe(true); + }); + + it("should fail do-repeatIf block with embedded if-else", () => { + expect(SuiteValidator.validateCommands(["do", "if", "blob", "else", "repeatIf"])).not.toBe(true); + }); + + it("should fail do-repeatIf block with embedded times", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "times", "blob", "repeatIf"])).not.toBe(true); + }); + + it("should fail do-repeatIf block with embedded times-end", () => { + expect(SuiteValidator.validateCommands(["do", "blob", "times", "blob", "end"])).not.toBe(true); + }); + + + it("should fail with continue outside of the loop", () => { + expect(SuiteValidator.validateCommands(["continue", "do", "if", "blob", "else", "end", "repeatIf"])).not.toBe(true); + }); + + + it("should fail with break outside of the loop", () => { + expect(SuiteValidator.validateCommands(["break", "do", "if", "blob", "else", "end", "repeatIf"])).not.toBe(true); + }); + +}); From c695d788dd515910da01c373b81ad0877bc8cbae Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 6 Jun 2018 16:06:06 +0300 Subject: [PATCH 22/23] Sandboxing work started --- .../selenium-ide/src/content/commands-api.js | 14 +++++ packages/selenium-ide/src/manifest.json | 3 + packages/selenium-ide/src/neo/index.html | 1 + packages/selenium-ide/src/neo/stores/seed.js | 59 +++++++++++++++---- .../selenium-ide/src/sandbox/sandbox.html | 41 +++++++++++++ packages/selenium-ide/webpack.config.babel.js | 1 + 6 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 packages/selenium-ide/src/sandbox/sandbox.html diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 896e7af4b8..5a99588116 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -42,6 +42,20 @@ function doCommands(request, sender, sendResponse) { selenium["doDomWait"]("", selenium.preprocessParameter("")); sendResponse({ dom_time: window.sideex_new_page }); } else if (isConditinal(request.commands)) { + window.addEventListener("message", function(event) { + console.log("event: ", event); + console.log("sandbox eval result:", event.data.evaluationResult); + }); + + let iframe = document.getElementById("evalSandboxFrame"); + let message = { + command: "evaluateCommand", + evaluationCommand: "'test' + 1" + }; + iframe.contentWindow.postMessage(message, "*"); + + console.log(document); + executeConditional(request, sendResponse); } else { const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); diff --git a/packages/selenium-ide/src/manifest.json b/packages/selenium-ide/src/manifest.json index 0790a93ec5..0e6132102f 100644 --- a/packages/selenium-ide/src/manifest.json +++ b/packages/selenium-ide/src/manifest.json @@ -51,5 +51,8 @@ "background": { "scripts": ["assets/background.js"] + }, + "sandbox": { + "pages": ["assets/sandbox.html"] } } diff --git a/packages/selenium-ide/src/neo/index.html b/packages/selenium-ide/src/neo/index.html index 2b127bf53c..e3ba78c685 100644 --- a/packages/selenium-ide/src/neo/index.html +++ b/packages/selenium-ide/src/neo/index.html @@ -8,6 +8,7 @@
+ diff --git a/packages/selenium-ide/src/neo/stores/seed.js b/packages/selenium-ide/src/neo/stores/seed.js index 11db40b383..0cd2524af7 100644 --- a/packages/selenium-ide/src/neo/stores/seed.js +++ b/packages/selenium-ide/src/neo/stores/seed.js @@ -60,23 +60,58 @@ export default function seed(store, numberOfSuites = 5) { const firstClick = playbackTest.createCommand(); firstClick.setCommand("click"); firstClick.setTarget("link=enacted"); - const secondClick = playbackTest.createCommand(); - secondClick.setCommand("clickAt"); - secondClick.setTarget("link=parliamentary systems"); + const storeCommand = playbackTest.createCommand(); + storeCommand.setCommand("store"); + storeCommand.setTarget("value 1"); + storeCommand.setValue("variable1"); + const ifCommand = playbackTest.createCommand(); + ifCommand.setCommand("if"); + ifCommand.setTarget("variable1 === 'value1'"); + const assertText = playbackTest.createCommand(); + assertText.setCommand("assertText"); + assertText.setTarget("id=Approval"); + assertText.setValue("Approval"); + const elseIfCommand = playbackTest.createCommand(); + elseIfCommand.setCommand("elseIf"); + elseIfCommand.setTarget("1 === 1"); + const verifyEditableCommand = playbackTest.createCommand(); + verifyEditableCommand.setCommand("verifyEditable"); + verifyEditableCommand.setTarget("id=searchInput"); + const endCommand = playbackTest.createCommand(); + endCommand.setCommand("end"); + endCommand.setTarget(""); + const echo = playbackTest.createCommand(); + echo.setCommand("echo"); + echo.setTarget("Finished!"); const playbackTest2 = store.createTestCase("aab playback"); const open2 = playbackTest2.createCommand(); open2.setCommand("open"); open2.setTarget("/wiki/River_Chater"); - const firstClick2 = playbackTest2.createCommand(); - firstClick2.setCommand("clickAt"); - firstClick2.setTarget("link=River Welland"); - const secondClick2 = playbackTest2.createCommand(); - secondClick2.setCommand("clickAt"); - secondClick2.setTarget("link=floods of 1947"); - const thirdClick2 = playbackTest2.createCommand(); - thirdClick2.setCommand("clickAt"); - thirdClick2.setTarget("link=scapegoat"); + const whileCommand = playbackTest2.createCommand(); + whileCommand.setCommand("while"); + whileCommand.setTarget("1 === 1"); + const echoCommand = playbackTest2.createCommand(); + echoCommand.setCommand("echo"); + echoCommand.setTarget("iteration"); + const doCommand = playbackTest2.createCommand(); + doCommand.setCommand("do"); + doCommand.setTarget(""); + const ifCommandd = playbackTest2.createCommand(); + ifCommandd.setCommand("if"); + ifCommandd.setTarget("1 === 1"); + const continueCommand = playbackTest2.createCommand(); + continueCommand.setCommand("continue"); + continueCommand.setTarget(""); + const endCommandd = playbackTest2.createCommand(); + endCommandd.setCommand("end"); + endCommandd.setTarget(""); + const repeatIfCommand = playbackTest2.createCommand(); + repeatIfCommand.setCommand("repeatIf"); + repeatIfCommand.setTarget("1 === 2"); + const endCommandd2 = playbackTest2.createCommand(); + endCommandd2.setCommand("end"); + endCommandd2.setTarget(""); const typeTest = store.createTestCase("aab type"); const open3 = typeTest.createCommand(); diff --git a/packages/selenium-ide/src/sandbox/sandbox.html b/packages/selenium-ide/src/sandbox/sandbox.html new file mode 100644 index 0000000000..dc217d3fdb --- /dev/null +++ b/packages/selenium-ide/src/sandbox/sandbox.html @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/packages/selenium-ide/webpack.config.babel.js b/packages/selenium-ide/webpack.config.babel.js index 7e711f4555..d4eae5dc46 100644 --- a/packages/selenium-ide/webpack.config.babel.js +++ b/packages/selenium-ide/webpack.config.babel.js @@ -236,6 +236,7 @@ export default { { from: "content/prompt.js", to: "./" }, { from: "content/prompt.css", to: "./" }, { from: "content/bootstrap.html", to: "./" }, + { from: "sandbox/sandbox.html", to: "./" }, { from: "manifest.json", to: "../" }, { from: "icons", to: "../icons" } ]), From 6cfdb610a8d019ba011b869c9ad547aae6666694 Mon Sep 17 00:00:00 2001 From: Oleg Vodolazsky Date: Wed, 13 Jun 2018 14:16:29 +0300 Subject: [PATCH 23/23] Eval conditions in the sandbox with variables injected --- .../selenium-ide/src/content/commands-api.js | 46 ----------- .../src/neo/IO/SideeX/playback.js | 20 +++-- .../src/neo/IO/SideeX/sandboxCommand.js | 81 +++++++++++++++++++ .../selenium-ide/src/sandbox/sandbox.html | 5 -- 4 files changed, 96 insertions(+), 56 deletions(-) create mode 100644 packages/selenium-ide/src/neo/IO/SideeX/sandboxCommand.js diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 5a99588116..d630ea3881 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -41,22 +41,6 @@ function doCommands(request, sender, sendResponse) { } else if (request.commands == "domWait") { selenium["doDomWait"]("", selenium.preprocessParameter("")); sendResponse({ dom_time: window.sideex_new_page }); - } else if (isConditinal(request.commands)) { - window.addEventListener("message", function(event) { - console.log("event: ", event); - console.log("sandbox eval result:", event.data.evaluationResult); - }); - - let iframe = document.getElementById("evalSandboxFrame"); - let message = { - command: "evaluateCommand", - evaluationCommand: "'test' + 1" - }; - iframe.contentWindow.postMessage(message, "*"); - - console.log(document); - - executeConditional(request, sendResponse); } else { const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); if (selenium["do" + upperCase] != null) { @@ -128,36 +112,6 @@ function doCommands(request, sender, sendResponse) { } } -function isConditinal(commandName) { - return ["if", "while", "elseIf", "repeatIf", "times"].includes(commandName); -} - -function executeConditional(request, sendResponse) { - - const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); - if (selenium["do" + upperCase] != null) { - try { - document.body.setAttribute("SideeXPlayingFlag", true); - let returnValue = selenium["do"+upperCase](request.target,selenium.preprocessParameter(request.value)); - // conditional command executed! - if (returnValue) { - document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: "truthy"}); - } else { - document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: "falsy"}); - } - } catch(e) { - // Synchronous command failed - document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: e.message}); - } - } else { - sendResponse({ result: "Unknown command: " + request.commands }); - } - -} - if (!window._listener) { window._listener = doCommands; browser.runtime.onMessage.addListener(doCommands); diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index c9f6089835..5261246e16 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -21,8 +21,10 @@ import UiState from "../../stores/view/UiState"; import ExtCommand, { isExtCommand } from "./ext-command"; import { xlateArgument } from "./formatCommand"; import PlaybackTree from "./playbackTree"; +import SandboxCommand from "./sandboxCommand"; export const extCommand = new ExtCommand(); +const sandboxCommand = new SandboxCommand(); // In order to not break the separation of the execution loop from the state of the playback // I will set doSetSpeed here so that extCommand will not be aware of the state extCommand.doSetSpeed = (speed) => { @@ -260,11 +262,15 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { }, 500); }); - return p.then(() => ( - command !== "type" - ? extCommand.sendMessage(command, xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command)) - : extCommand.doType(xlateArgument(target), xlateArgument(value)) - )) + return p.then(() => { + if (command === "type") { + return extCommand.doType(xlateArgument(target), xlateArgument(value)); + } else if (isConditinal(command)) { + return sandboxCommand.evalExpression(target); + } else { + return extCommand.sendMessage(command, xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command)); + } + }) .then(function(result) { if (result.result === "truthy") { return true; @@ -295,6 +301,10 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { }); } +function isConditinal(commandName) { + return ["if", "while", "elseIf", "repeatIf", "times"].includes(commandName); +} + function doDelay() { return new Promise((res) => { if (PlaybackState.currentPlayingIndex + 1 === PlaybackState.runningQueue.length) { diff --git a/packages/selenium-ide/src/neo/IO/SideeX/sandboxCommand.js b/packages/selenium-ide/src/neo/IO/SideeX/sandboxCommand.js new file mode 100644 index 0000000000..268428ddd6 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/SideeX/sandboxCommand.js @@ -0,0 +1,81 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { xlateArgument } from "./formatCommand"; + +export default class SandboxCommand { + constructor() { + this.result = null; + this.iframe = document.getElementById("evalSandboxFrame"); + this.attachWatcher = this.attachWatcher.bind(this); + this.attachWatcher(); + } + + attachWatcher() { + window.addEventListener("message", (event) => { + if (event.data.evaluationResult) { + this.result = event.data.evaluationResult; + } + }); + } + + wait(...properties) { + if (!properties.length) + return Promise.reject("No arguments"); + let self = this; + let ref = this; + let inspecting = properties[properties.length - 1]; + for (let i = 0; i < properties.length - 1; i++) { + if (!ref[properties[i]] | !(ref[properties[i]] instanceof Array | ref[properties[i]] instanceof Object)) + return Promise.reject("Invalid Argument"); + ref = ref[properties[i]]; + } + return new Promise(function(resolve, reject) { + let counter = 0; + let interval = setInterval(function() { + if (ref[inspecting] === undefined || ref[inspecting] === false) { + counter++; + if (counter > self.waitTimes) { + reject("Timeout"); + clearInterval(interval); + } + } else { + resolve(); + clearInterval(interval); + } + }, self.waitInterval); + }); + } + + evalExpression(expression) { + // redo after + const message = { + command: "evaluateCommand", + evaluationCommand: xlateArgument(expression) + }; + this.iframe.contentWindow.postMessage(message, "*"); + return this.wait("result") + .then(() => { + let result = "falsy"; + if (this.result) { + result = "truthy"; + } + this.result = null; + return Promise.resolve({result: result}); + }); + } +}; diff --git a/packages/selenium-ide/src/sandbox/sandbox.html b/packages/selenium-ide/src/sandbox/sandbox.html index dc217d3fdb..48cd346c00 100644 --- a/packages/selenium-ide/src/sandbox/sandbox.html +++ b/packages/selenium-ide/src/sandbox/sandbox.html @@ -8,11 +8,6 @@ -