From 31c56ac611f9fe7f7f9bb0a827c4f49924b59699 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 27 Jun 2018 10:08:07 -0400 Subject: [PATCH 001/125] Tests and code (embedded in the test file) for control flow validation. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js new file mode 100644 index 000000000..326175aed --- /dev/null +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -0,0 +1,150 @@ +// 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. + +class Automata { + constructor(stack) { + this.stack = stack; + } + + process() { + let stack = this.stack; + let _stack = []; + let topOf = this.topOf; + stack.forEach(function(commandName, index) { + switch(commandName) { + case "if": + case "while": + case "times": + case "repeatIf": + _stack.push({ name: commandName, index: index }); + break; + case "else": + case "elseIf": + if (topOf(_stack).name !== "if") { + throw "An else / elseIf used outside of an if block"; + } + break; + case "end": + if (topOf(_stack).name === "while" || + topOf(_stack).name === "times" || + topOf(_stack).name === "repeatIf") { + _stack.pop(); + } else if (topOf(_stack).name === "if") { + const segment = stack.slice(topOf(_stack).index, index); + const elseCount = segment.filter(name => name === "else").length; + if (elseCount > 1) { + throw "Too many else commands used"; + } else if (elseCount === 1 && topOf(segment) !== "else") { + throw "Incorrect command order of elseIf / else"; + } else if (elseCount === 0 || topOf(segment) === "else") { + _stack.pop(); + } + } + break; + } + }); + if (_stack.length > 0) { + throw "Incomplete block at " + topOf(_stack).name; + } else { + return true; + } + } + + topOf(stack) { + return stack[stack.length - 1]; + } + +} + +describe("Control Flow Validation", () => { + test("if", () => { + let automata = new Automata(["if"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at if"); + }); + test("if, if, end", () => { + let automata = new Automata(["if", "if", "end"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at if"); + }); + test("if, end", () => { + let automata = new Automata(["if", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("if, else, end", () => { + let automata = new Automata(["if", "else", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("if, elseIf, end", () => { + let automata = new Automata(["if", "elseIf", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("if, elseIf, else, end", () => { + let automata = new Automata(["if", "elseIf", "else", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("if, else, elseIf, end", () => { + let automata = new Automata(["if", "else", "elseIf", "end"]); + expect(function() { automata.process(); }).toThrow("Incorrect command order of elseIf / else"); + }); + test("if, else, else, end", () => { + let automata = new Automata(["if", "else", "else", "end"]); + expect(function() { automata.process(); }).toThrow("Too many else commands used"); + }); + test("while", () => { + let automata = new Automata(["while"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at while"); + }); + test("while, end", () => { + let automata = new Automata(["while", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("if, while", () => { + let automata = new Automata(["if", "else", "elseIf", "while"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at while"); + }); + test("if, while, end", () => { + let automata = new Automata(["if", "else", "elseIf", "while", "end"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at if"); + }); + test("if, while, else, end", () => { + let automata = new Automata(["if", "else", "elseIf", "while", "else", "end"]); + expect(function() { automata.process(); }).toThrow("An else / elseIf used outside of an if block"); + }); + test("times", () => { + let automata = new Automata(["times"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at times"); + }); + test("times, end", () => { + let automata = new Automata(["times", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("repeatIf", () => { + let automata = new Automata(["repeatIf"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at repeatIf"); + }); + test("repeatIf, end", () => { + let automata = new Automata(["repeatIf", "end"]); + expect(automata.process()).toBeTruthy(); + }); + test("repeatIf, if", () => { + let automata = new Automata(["repeatIf", "if"]); + expect(function() { automata.process(); }).toThrow("Incomplete block at if"); + }); + test("repeatIf, if, end, end", () => { + let automata = new Automata(["repeatIf", "if", "end", "end"]); + expect(automata.process()).toBeTruthy(); + }); +}); From b4c7049d8eec3972507c9b4f83f4fd24cdd0d0f7 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 28 Jun 2018 09:37:16 -0400 Subject: [PATCH 002/125] Fix for if/else leveling. Broke things into preprocess (leveling and syntax validation) and process (generating the linked list) test groups. Processing is still WIP. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 298 ++++++++++++------ 1 file changed, 202 insertions(+), 96 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 326175aed..6f49a56ad 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -15,136 +15,242 @@ // specific language governing permissions and limitations // under the License. +class CommandNode { + constructor() { + this.next = undefined; + this.left = undefined; + this.right = undefined; + } +} + class Automata { constructor(stack) { - this.stack = stack; + this.inputStack = stack; + this.preprocessStack = []; + this.stack = []; } - process() { - let stack = this.stack; - let _stack = []; + preprocess() { let topOf = this.topOf; - stack.forEach(function(commandName, index) { - switch(commandName) { + let level = 0; + let state = []; + let stack = []; + Object.assign(stack, this.inputStack); + this.inputStack.forEach(function(command, index) { + switch(command.name) { case "if": case "while": case "times": case "repeatIf": - _stack.push({ name: commandName, index: index }); + Object.assign(stack[index], { level: level }); + level++; + state.push({ name: command.name, index: index }); break; case "else": case "elseIf": - if (topOf(_stack).name !== "if") { + if (topOf(state).name !== "if") { throw "An else / elseIf used outside of an if block"; } + level--; + Object.assign(stack[index], { level: level }); + level++; break; case "end": - if (topOf(_stack).name === "while" || - topOf(_stack).name === "times" || - topOf(_stack).name === "repeatIf") { - _stack.pop(); - } else if (topOf(_stack).name === "if") { - const segment = stack.slice(topOf(_stack).index, index); - const elseCount = segment.filter(name => name === "else").length; + if (topOf(state).name === "while" || + topOf(state).name === "times" || + topOf(state).name === "repeatIf") { + level--; + Object.assign(stack[index], { level: level }); + state.pop(); + } else if (topOf(state).name === "if") { + const segment = stack.slice(topOf(state).index, index); + const elseCount = segment.filter(kommand => kommand.name === "else").length; + const elseSegment = segment.filter(kommand => kommand.name.match(/else/)); if (elseCount > 1) { throw "Too many else commands used"; - } else if (elseCount === 1 && topOf(segment) !== "else") { + } else if (elseCount === 1 && topOf(elseSegment).name !== "else") { throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || topOf(segment) === "else") { - _stack.pop(); + } else if (elseCount === 0 || topOf(elseSegment).name === "else") { + level--; + Object.assign(stack[index], { level: level }); + state.pop(); } } break; + default: + Object.assign(stack[index], { level: level }); + break; } }); - if (_stack.length > 0) { - throw "Incomplete block at " + topOf(_stack).name; + if (state.length > 0) { + throw "Incomplete block at " + topOf(state).name; } else { - return true; + this.preprocessStack = stack; + return this.preprocessStack; } } + process() { + let _stack = this.preprocessStack; + let stack = this.stack; + this.preprocessStack.forEach(function(command, index) { + if (_stack[index + 1] && (command.level === _stack[index + 1].level)) { + let node = new CommandNode; + node.next = _stack[index + 1]; + stack.push(node); + } else { + let node = new CommandNode; + node.right = _stack[index + 1]; + let segment = _stack.slice(index + 1, _stack.length + 1); + let leftTarget = segment.findIndex(kommand => kommand.level === command.level); + console.log(leftTarget); + for(let i = index; i < _stack.length + 1; i++) { + if (_stack[i + 1] && (_stack[i].level === _stack[i + 1].level)) { + console.log("HELLO!"); + node.left = _stack[i + 1]; + break; + } + } + stack.push(node); + } + }); + } + topOf(stack) { return stack[stack.length - 1]; } } -describe("Control Flow Validation", () => { - test("if", () => { - let automata = new Automata(["if"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at if"); - }); - test("if, if, end", () => { - let automata = new Automata(["if", "if", "end"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at if"); - }); - test("if, end", () => { - let automata = new Automata(["if", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("if, else, end", () => { - let automata = new Automata(["if", "else", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("if, elseIf, end", () => { - let automata = new Automata(["if", "elseIf", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("if, elseIf, else, end", () => { - let automata = new Automata(["if", "elseIf", "else", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("if, else, elseIf, end", () => { - let automata = new Automata(["if", "else", "elseIf", "end"]); - expect(function() { automata.process(); }).toThrow("Incorrect command order of elseIf / else"); - }); - test("if, else, else, end", () => { - let automata = new Automata(["if", "else", "else", "end"]); - expect(function() { automata.process(); }).toThrow("Too many else commands used"); - }); - test("while", () => { - let automata = new Automata(["while"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at while"); - }); - test("while, end", () => { - let automata = new Automata(["while", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("if, while", () => { - let automata = new Automata(["if", "else", "elseIf", "while"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at while"); - }); - test("if, while, end", () => { - let automata = new Automata(["if", "else", "elseIf", "while", "end"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at if"); - }); - test("if, while, else, end", () => { - let automata = new Automata(["if", "else", "elseIf", "while", "else", "end"]); - expect(function() { automata.process(); }).toThrow("An else / elseIf used outside of an if block"); - }); - test("times", () => { - let automata = new Automata(["times"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at times"); - }); - test("times, end", () => { - let automata = new Automata(["times", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("repeatIf", () => { - let automata = new Automata(["repeatIf"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at repeatIf"); - }); - test("repeatIf, end", () => { - let automata = new Automata(["repeatIf", "end"]); - expect(automata.process()).toBeTruthy(); - }); - test("repeatIf, if", () => { - let automata = new Automata(["repeatIf", "if"]); - expect(function() { automata.process(); }).toThrow("Incomplete block at if"); +describe("Control Flow", () => { + describe("Preprocess", () => { + test("marked with correct levels", () => { + let automataFactory = new Automata([ + { name: "if" }, + { name: "command" }, + { name: "else" }, + { name: "while" }, + { name: "command" }, + { name: "end" }, + { name: "end" } + ]); + let automata = automataFactory.preprocess(); + expect(automata[0].level).toEqual(0); // if + expect(automata[1].level).toEqual(1); // command + expect(automata[2].level).toEqual(0); // else + expect(automata[3].level).toEqual(1); // while + expect(automata[4].level).toEqual(2); // command + expect(automata[5].level).toEqual(1); // end + expect(automata[6].level).toEqual(0); // end + }); + describe("Validation", () => { + test("if, end", () => { + let automata = new Automata([{ name: "if" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("if, else, end", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("if, elseIf, end", () => { + let automata = new Automata([{ name: "if" }, { name: "elseIf" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("if, elseIf, else, end", () => { + let automata = new Automata([{ name: "if" }, { name: "elseIf" }, { name: "else" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("while, end", () => { + let automata = new Automata([{ name: "while" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("times, end", () => { + let automata = new Automata([{ name: "times" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("repeatIf, end", () => { + let automata = new Automata([{ name: "repeatIf" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + test("repeatIf, if, end, end", () => { + let automata = new Automata([{ name: "repeatIf" }, { name: "if" }, { name: "end" }, { name: "end" }]); + expect(automata.preprocess()).toBeTruthy(); + }); + }); + describe("Invalidation", () => { + test("if", () => { + let automata = new Automata([{ name: "if" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + }); + test("if, if, end", () => { + let automata = new Automata([{ name: "if" }, { name: "if" }, { name: "end" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + }); + test("if, else, elseIf, end", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "end" }]); + expect(function() { automata.preprocess(); }).toThrow("Incorrect command order of elseIf / else"); + }); + test("if, else, else, end", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "else" }, { name: "end" }]); + expect(function() { automata.preprocess(); }).toThrow("Too many else commands used"); + }); + test("while", () => { + let automata = new Automata([{ name: "while" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at while"); + }); + test("if, while", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at while"); + }); + test("if, while, end", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "end" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + }); + test("if, while, else, end", () => { + let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "else" }, { name: "end" }]); + expect(function() { automata.preprocess(); }).toThrow("An else / elseIf used outside of an if block"); + }); + test("times", () => { + let automata = new Automata([{ name: "times" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at times"); + }); + test("repeatIf", () => { + let automata = new Automata([{ name: "repeatIf" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at repeatIf"); + }); + test("repeatIf, if", () => { + let automata = new Automata([{ name: "repeatIf" }, { name: "if" }]); + expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + }); + }); }); - test("repeatIf, if, end, end", () => { - let automata = new Automata(["repeatIf", "if", "end", "end"]); - expect(automata.process()).toBeTruthy(); + + describe.skip("Process", () => { + describe("Generate Linked List", () => { + test("has next for command-command", () => { + let input = [ + { name: "command1" }, + { name: "command2" } + ]; + let automata = new Automata(input); + automata.preprocess(); + automata.process(); + expect(automata.stack[0].next).toEqual(input[1]); + }); + + test("has next and left, right for if-command-end", () => { + let input = [ + { name: "if" }, + { name: "command" }, + { name: "end" } + ]; + let automata = new Automata(input); + automata.preprocess(); + automata.process(); + expect(automata.stack[0].next).toBeUndefined(); + expect(automata.stack[0].right).toEqual(input[1]); + expect(automata.stack[0].left).toEqual(input[2]); + }); + }); }); }); From 46a2a694e70feb9c6ce99afaf47bd4e2ea167186 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 28 Jun 2018 15:22:31 -0400 Subject: [PATCH 003/125] WIP on cleaning up code into a more OO design. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 110 +++++++++++++++--- 1 file changed, 95 insertions(+), 15 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 6f49a56ad..394f54ecf 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -23,6 +23,83 @@ class CommandNode { } } +class Level { + constructor(_stack) { + this.level = 0; + this.stack = []; + Object.assign(this.stack, _stack); + } + + increase() { + this.level++; + } + + decrease() { + this.level--; + } + + store(index) { + Object.assign(this.stack[index], { level: this.level }); + } +} + +class Command { + constructor(command, commandProcessor) { + switch(command.name) { + case "if": + return new If(command, commandProcessor); + case "end": + return new End(command, commandProcessor); + default: + // return generic command object + break; + } + } +} + +class ICommand { + constructor(command, commandProcessor) { + this.command = command; + this.commandProcessor = commandProcessor; + } + + validate() { + } + + level() { + } + + setState() { + } +} + +class If extends ICommand { + constructor(command, commandProcessor) { + super(command, commandProcessor); + } + + level() { + this.commandProcessor.level.store(this.commandProcessor.index); + this.commandProcessor.level.increase(); + } + + setState() { + this.commandProcessor.state.push({ + name: this.command.name, + index: this.commandProcessor.index + }); + } + + process() { + } +} + +class End extends ICommand { + constructor(command, commandProcessor) { + super(command, commandProcessor); + } +} + class Automata { constructor(stack) { this.inputStack = stack; @@ -32,18 +109,21 @@ class Automata { preprocess() { let topOf = this.topOf; - let level = 0; let state = []; - let stack = []; - Object.assign(stack, this.inputStack); + let level = new Level(this.inputStack); this.inputStack.forEach(function(command, index) { + let cmd = new Command(command, { index, level, state }); switch(command.name) { case "if": + cmd.validate(); + cmd.level(); + cmd.setState(); + break; case "while": case "times": case "repeatIf": - Object.assign(stack[index], { level: level }); - level++; + level.store(index); + level.increase(); state.push({ name: command.name, index: index }); break; case "else": @@ -51,19 +131,19 @@ class Automata { if (topOf(state).name !== "if") { throw "An else / elseIf used outside of an if block"; } - level--; - Object.assign(stack[index], { level: level }); - level++; + level.decrease(); + level.store(index); + level.increase(); break; case "end": if (topOf(state).name === "while" || topOf(state).name === "times" || topOf(state).name === "repeatIf") { - level--; - Object.assign(stack[index], { level: level }); + level.decrease(); + level.store(index); state.pop(); } else if (topOf(state).name === "if") { - const segment = stack.slice(topOf(state).index, index); + const segment = level.stack.slice(topOf(state).index, index); const elseCount = segment.filter(kommand => kommand.name === "else").length; const elseSegment = segment.filter(kommand => kommand.name.match(/else/)); if (elseCount > 1) { @@ -71,21 +151,21 @@ class Automata { } else if (elseCount === 1 && topOf(elseSegment).name !== "else") { throw "Incorrect command order of elseIf / else"; } else if (elseCount === 0 || topOf(elseSegment).name === "else") { - level--; - Object.assign(stack[index], { level: level }); + level.decrease(); + level.store(index); state.pop(); } } break; default: - Object.assign(stack[index], { level: level }); + level.store(index); break; } }); if (state.length > 0) { throw "Incomplete block at " + topOf(state).name; } else { - this.preprocessStack = stack; + this.preprocessStack = level.stack; return this.preprocessStack; } } From 95967b16e1f5bcd27b3a65fa4d7454acf76328ff Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 1 Jul 2018 09:26:56 -0400 Subject: [PATCH 004/125] Refactored control flow preprocessing. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 364 ++++++++++-------- 1 file changed, 202 insertions(+), 162 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 394f54ecf..a0b7a2c70 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -23,84 +23,177 @@ class CommandNode { } } -class Level { - constructor(_stack) { - this.level = 0; - this.stack = []; - Object.assign(this.stack, _stack); +class Command { + constructor(command, commandStackHandler) { + switch(command.name) { + case "if": + return new If(command, commandStackHandler); + case "else": + case "elseIf": + return new Else(command, commandStackHandler); + case "while": + case "times": + case "repeatIf": + return new Loop(command, commandStackHandler); + case "end": + return new End(command, commandStackHandler); + default: + return new Default(command, commandStackHandler); + } } +} - increase() { - this.level++; +class If { + constructor(command, commandStackHandler) { + this.command = command; + this.commandStackHandler = commandStackHandler; } - decrease() { - this.level--; + preprocess() { + this.commandStackHandler.store(); + this.commandStackHandler.setState(); + this.commandStackHandler.increaseLevel(); } +} - store(index) { - Object.assign(this.stack[index], { level: this.level }); +class Else { + constructor(command, commandStackHandler) { + this.command = command; + this.commandStackHandler = commandStackHandler; } -} -class Command { - constructor(command, commandProcessor) { - switch(command.name) { - case "if": - return new If(command, commandProcessor); - case "end": - return new End(command, commandProcessor); - default: - // return generic command object - break; + preprocess() { + if (this.commandStackHandler.top().name !== "if") { + throw "An else / elseIf used outside of an if block"; } + this.commandStackHandler.decreaseLevel(); + this.commandStackHandler.store(); + this.commandStackHandler.increaseLevel(); } } -class ICommand { - constructor(command, commandProcessor) { +class Loop { + constructor(command, commandStackHandler) { this.command = command; - this.commandProcessor = commandProcessor; + this.commandStackHandler = commandStackHandler; } - validate() { + preprocess() { + this.commandStackHandler.store(); + this.commandStackHandler.setState(); + this.commandStackHandler.increaseLevel(); } - level() { +} + +class End { + constructor(command, commandStackHandler) { + this.command = command; + this.commandStackHandler = commandStackHandler; } - setState() { + preprocess() { + if (this.terminatesLoop()) { + this.commandStackHandler.decreaseLevel(); + this.commandStackHandler.store(); + this.commandStackHandler.popState(); + } else if (this.terminatesIf()) { + const segment = this.commandStackHandler.segment( + this.commandStackHandler.top().index, + this.commandStackHandler.currentCommandIndex + ); + const elseCount = segment.filter(command => command.name === "else").length; + const elseSegment = segment.filter(command => command.name.match(/else/)); + if (elseCount > 1) { + throw "Too many else commands used"; + } else if (elseCount === 1 && this.commandStackHandler.topOf(elseSegment).name !== "else") { + throw "Incorrect command order of elseIf / else"; + } else if (elseCount === 0 || this.commandStackHandler.topOf(elseSegment).name === "else") { + this.commandStackHandler.decreaseLevel(); + this.commandStackHandler.store(); + this.commandStackHandler.popState(); + } + } + } + + terminatesIf() { + return (this.commandStackHandler.top().name === "if"); + } + + terminatesLoop() { + return (this.commandStackHandler.top().name === "while" || + this.commandStackHandler.top().name === "times" || + this.commandStackHandler.top().name === "repeatIf"); } + } -class If extends ICommand { - constructor(command, commandProcessor) { - super(command, commandProcessor); +class Default { + constructor(command, commandStackHandler) { + this.command = command; + this.commandStackHandler = commandStackHandler; + } + + preprocess() { + this.commandStackHandler.store(); + } +} + +class CommandStackHandler { + constructor(stack) { + this.stack = []; + Object.assign(this.stack, stack); + this.state = []; + this.level = 0; + this.currentCommand; + this.currentCommandIndex; + } + + increaseLevel() { + this.level++; + } + + decreaseLevel() { + this.level--; } - level() { - this.commandProcessor.level.store(this.commandProcessor.index); - this.commandProcessor.level.increase(); + store() { + Object.assign(this.stack[this.currentCommandIndex], { level: this.level }); } setState() { - this.commandProcessor.state.push({ - name: this.command.name, - index: this.commandProcessor.index - }); + this.state.push({ name: this.currentCommand.name, index: this.currentCommandIndex }); } - process() { + popState() { + this.state.pop(); + } + + top() { + return this.state[this.state.length - 1]; + } + + topOf(segment) { + return segment[segment.length - 1]; + } + + segment(startIndex, endIndex) { + return this.stack.slice(startIndex, endIndex); + } + + setCurrentCommand(command, index) { + this.currentCommand = command; + this.currentCommandIndex = index; } -} -class End extends ICommand { - constructor(command, commandProcessor) { - super(command, commandProcessor); + confirmation() { + if (this.state.length > 0) { + throw "Incomplete block at " + this.top().name; + } } } -class Automata { +class PlaybackTree { constructor(stack) { this.inputStack = stack; this.preprocessStack = []; @@ -108,66 +201,15 @@ class Automata { } preprocess() { - let topOf = this.topOf; - let state = []; - let level = new Level(this.inputStack); - this.inputStack.forEach(function(command, index) { - let cmd = new Command(command, { index, level, state }); - switch(command.name) { - case "if": - cmd.validate(); - cmd.level(); - cmd.setState(); - break; - case "while": - case "times": - case "repeatIf": - level.store(index); - level.increase(); - state.push({ name: command.name, index: index }); - break; - case "else": - case "elseIf": - if (topOf(state).name !== "if") { - throw "An else / elseIf used outside of an if block"; - } - level.decrease(); - level.store(index); - level.increase(); - break; - case "end": - if (topOf(state).name === "while" || - topOf(state).name === "times" || - topOf(state).name === "repeatIf") { - level.decrease(); - level.store(index); - state.pop(); - } else if (topOf(state).name === "if") { - const segment = level.stack.slice(topOf(state).index, index); - const elseCount = segment.filter(kommand => kommand.name === "else").length; - const elseSegment = segment.filter(kommand => kommand.name.match(/else/)); - if (elseCount > 1) { - throw "Too many else commands used"; - } else if (elseCount === 1 && topOf(elseSegment).name !== "else") { - throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || topOf(elseSegment).name === "else") { - level.decrease(); - level.store(index); - state.pop(); - } - } - break; - default: - level.store(index); - break; - } + let commandStackHandler = new CommandStackHandler(this.inputStack); + this.inputStack.forEach(function(currentCommand, currentCommandIndex) { + commandStackHandler.setCurrentCommand(currentCommand, currentCommandIndex); + let command = new Command(currentCommand, commandStackHandler); + command.preprocess(); }); - if (state.length > 0) { - throw "Incomplete block at " + topOf(state).name; - } else { - this.preprocessStack = level.stack; - return this.preprocessStack; - } + commandStackHandler.confirmation(); + this.preprocessStack = commandStackHandler.stack; + return this.preprocessStack; } process() { @@ -183,10 +225,8 @@ class Automata { node.right = _stack[index + 1]; let segment = _stack.slice(index + 1, _stack.length + 1); let leftTarget = segment.findIndex(kommand => kommand.level === command.level); - console.log(leftTarget); for(let i = index; i < _stack.length + 1; i++) { if (_stack[i + 1] && (_stack[i].level === _stack[i + 1].level)) { - console.log("HELLO!"); node.left = _stack[i + 1]; break; } @@ -205,7 +245,7 @@ class Automata { describe("Control Flow", () => { describe("Preprocess", () => { test("marked with correct levels", () => { - let automataFactory = new Automata([ + let _playbackTree = new PlaybackTree([ { name: "if" }, { name: "command" }, { name: "else" }, @@ -214,93 +254,93 @@ describe("Control Flow", () => { { name: "end" }, { name: "end" } ]); - let automata = automataFactory.preprocess(); - expect(automata[0].level).toEqual(0); // if - expect(automata[1].level).toEqual(1); // command - expect(automata[2].level).toEqual(0); // else - expect(automata[3].level).toEqual(1); // while - expect(automata[4].level).toEqual(2); // command - expect(automata[5].level).toEqual(1); // end - expect(automata[6].level).toEqual(0); // end + let playbackTree = _playbackTree.preprocess(); + expect(playbackTree[0].level).toEqual(0); // if + expect(playbackTree[1].level).toEqual(1); // command + expect(playbackTree[2].level).toEqual(0); // else + expect(playbackTree[3].level).toEqual(1); // while + expect(playbackTree[4].level).toEqual(2); // command + expect(playbackTree[5].level).toEqual(1); // end + expect(playbackTree[6].level).toEqual(0); // end }); describe("Validation", () => { test("if, end", () => { - let automata = new Automata([{ name: "if" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("if, else, end", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("if, elseIf, end", () => { - let automata = new Automata([{ name: "if" }, { name: "elseIf" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("if, elseIf, else, end", () => { - let automata = new Automata([{ name: "if" }, { name: "elseIf" }, { name: "else" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "else" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("while, end", () => { - let automata = new Automata([{ name: "while" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "while" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("times, end", () => { - let automata = new Automata([{ name: "times" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "times" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("repeatIf, end", () => { - let automata = new Automata([{ name: "repeatIf" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); test("repeatIf, if, end, end", () => { - let automata = new Automata([{ name: "repeatIf" }, { name: "if" }, { name: "end" }, { name: "end" }]); - expect(automata.preprocess()).toBeTruthy(); + let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "if" }, { name: "end" }, { name: "end" }]); + expect(playbackTree.preprocess()).toBeTruthy(); }); }); describe("Invalidation", () => { test("if", () => { - let automata = new Automata([{ name: "if" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + let playbackTree = new PlaybackTree([{ name: "if" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { - let automata = new Automata([{ name: "if" }, { name: "if" }, { name: "end" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "if" }, { name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "end" }]); - expect(function() { automata.preprocess(); }).toThrow("Incorrect command order of elseIf / else"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incorrect command order of elseIf / else"); }); test("if, else, else, end", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "else" }, { name: "end" }]); - expect(function() { automata.preprocess(); }).toThrow("Too many else commands used"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "else" }, { name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Too many else commands used"); }); test("while", () => { - let automata = new Automata([{ name: "while" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at while"); + let playbackTree = new PlaybackTree([{ name: "while" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at while"); }); test("if, while", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at while"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "end" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { - let automata = new Automata([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "else" }, { name: "end" }]); - expect(function() { automata.preprocess(); }).toThrow("An else / elseIf used outside of an if block"); + let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "else" }, { name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("An else / elseIf used outside of an if block"); }); test("times", () => { - let automata = new Automata([{ name: "times" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at times"); + let playbackTree = new PlaybackTree([{ name: "times" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at times"); }); test("repeatIf", () => { - let automata = new Automata([{ name: "repeatIf" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at repeatIf"); + let playbackTree = new PlaybackTree([{ name: "repeatIf" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at repeatIf"); }); test("repeatIf, if", () => { - let automata = new Automata([{ name: "repeatIf" }, { name: "if" }]); - expect(function() { automata.preprocess(); }).toThrow("Incomplete block at if"); + let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "if" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); }); }); }); @@ -312,10 +352,10 @@ describe("Control Flow", () => { { name: "command1" }, { name: "command2" } ]; - let automata = new Automata(input); - automata.preprocess(); - automata.process(); - expect(automata.stack[0].next).toEqual(input[1]); + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + expect(playbackTree.stack[0].next).toEqual(input[1]); }); test("has next and left, right for if-command-end", () => { @@ -324,12 +364,12 @@ describe("Control Flow", () => { { name: "command" }, { name: "end" } ]; - let automata = new Automata(input); - automata.preprocess(); - automata.process(); - expect(automata.stack[0].next).toBeUndefined(); - expect(automata.stack[0].right).toEqual(input[1]); - expect(automata.stack[0].left).toEqual(input[2]); + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + expect(playbackTree.stack[0].next).toBeUndefined(); + expect(playbackTree.stack[0].right).toEqual(input[1]); + expect(playbackTree.stack[0].left).toEqual(input[2]); }); }); }); From 4af827d23d25fcc00402bfac784916bd3a332bba Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 1 Jul 2018 13:04:14 -0400 Subject: [PATCH 005/125] Added do/while and do/repeatif support. Some additional cleanup as well. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 204 ++++++++++-------- 1 file changed, 119 insertions(+), 85 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index a0b7a2c70..78de59da4 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -15,14 +15,6 @@ // specific language governing permissions and limitations // under the License. -class CommandNode { - constructor() { - this.next = undefined; - this.left = undefined; - this.right = undefined; - } -} - class Command { constructor(command, commandStackHandler) { switch(command.name) { @@ -34,6 +26,7 @@ class Command { case "while": case "times": case "repeatIf": + case "do": return new Loop(command, commandStackHandler); case "end": return new End(command, commandStackHandler); @@ -79,9 +72,19 @@ class Loop { } preprocess() { - this.commandStackHandler.store(); - this.commandStackHandler.setState(); - this.commandStackHandler.increaseLevel(); + if (this.command.name === "repeatIf") { + if (this.commandStackHandler.top().name !== "do") { + throw "A repeatIf used without a do block"; + } + this.commandStackHandler.store(); + } else if (this.command.name === "while" && + this.commandStackHandler.top().name === "do") { + this.commandStackHandler.store(); + } else { + this.commandStackHandler.store(); + this.commandStackHandler.setState(); + this.commandStackHandler.increaseLevel(); + } } } @@ -98,12 +101,8 @@ class End { this.commandStackHandler.store(); this.commandStackHandler.popState(); } else if (this.terminatesIf()) { - const segment = this.commandStackHandler.segment( - this.commandStackHandler.top().index, - this.commandStackHandler.currentCommandIndex - ); - const elseCount = segment.filter(command => command.name === "else").length; - const elseSegment = segment.filter(command => command.name.match(/else/)); + const elseCount = this.commandStackHandler.currentSegment().filter(command => command.name === "else").length; + const elseSegment = this.commandStackHandler.currentSegment().filter(command => command.name.match(/else/)); if (elseCount > 1) { throw "Too many else commands used"; } else if (elseCount === 1 && this.commandStackHandler.topOf(elseSegment).name !== "else") { @@ -113,6 +112,8 @@ class End { this.commandStackHandler.store(); this.commandStackHandler.popState(); } + } else { + throw "Use of end without an opening keyword"; } } @@ -123,7 +124,7 @@ class End { terminatesLoop() { return (this.commandStackHandler.top().name === "while" || this.commandStackHandler.top().name === "times" || - this.commandStackHandler.top().name === "repeatIf"); + this.commandStackHandler.top().name === "do"); } } @@ -170,15 +171,20 @@ class CommandStackHandler { } top() { - return this.state[this.state.length - 1]; + let command = this.state[this.state.length - 1]; + if (command) { + return this.state[this.state.length - 1]; + } else { + return { name: "" }; + } } topOf(segment) { return segment[segment.length - 1]; } - segment(startIndex, endIndex) { - return this.stack.slice(startIndex, endIndex); + currentSegment() { + return this.stack.slice(this.top().index, this.currentCommandIndex); } setCurrentCommand(command, index) { @@ -193,6 +199,14 @@ class CommandStackHandler { } } +class CommandNode { + constructor() { + this.next = undefined; + this.left = undefined; + this.right = undefined; + } +} + class PlaybackTree { constructor(stack) { this.inputStack = stack; @@ -213,27 +227,27 @@ class PlaybackTree { } process() { - let _stack = this.preprocessStack; - let stack = this.stack; - this.preprocessStack.forEach(function(command, index) { - if (_stack[index + 1] && (command.level === _stack[index + 1].level)) { - let node = new CommandNode; - node.next = _stack[index + 1]; - stack.push(node); - } else { - let node = new CommandNode; - node.right = _stack[index + 1]; - let segment = _stack.slice(index + 1, _stack.length + 1); - let leftTarget = segment.findIndex(kommand => kommand.level === command.level); - for(let i = index; i < _stack.length + 1; i++) { - if (_stack[i + 1] && (_stack[i].level === _stack[i + 1].level)) { - node.left = _stack[i + 1]; - break; - } - } - stack.push(node); - } - }); + //let _stack = this.preprocessStack; + //let stack = this.stack; + //this.preprocessStack.forEach(function(command, index) { + // if (_stack[index + 1] && (command.level === _stack[index + 1].level)) { + // let node = new CommandNode; + // node.next = _stack[index + 1]; + // stack.push(node); + // } else { + // let node = new CommandNode; + // node.right = _stack[index + 1]; + // let segment = _stack.slice(index + 1, _stack.length + 1); + // let leftTarget = segment.findIndex(kommand => kommand.level === command.level); + // for(let i = index; i < _stack.length + 1; i++) { + // if (_stack[i + 1] && (_stack[i].level === _stack[i + 1].level)) { + // node.left = _stack[i + 1]; + // break; + // } + // } + // stack.push(node); + // } + //}); } topOf(stack) { @@ -243,6 +257,34 @@ class PlaybackTree { } describe("Control Flow", () => { + describe.skip("Process", () => { + describe("Generate Linked List", () => { + test("has next for command-command", () => { + let input = [ + { name: "command1" }, + { name: "command2" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + expect(playbackTree.stack[0].next).toEqual(input[1]); + }); + + test("has next and left, right for if-command-end", () => { + let input = [ + { name: "if" }, + { name: "command" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + expect(playbackTree.stack[0].next).toBeUndefined(); + expect(playbackTree.stack[0].right).toEqual(input[1]); + expect(playbackTree.stack[0].left).toEqual(input[2]); + }); + }); + }); describe("Preprocess", () => { test("marked with correct levels", () => { let _playbackTree = new PlaybackTree([ @@ -252,16 +294,32 @@ describe("Control Flow", () => { { name: "while" }, { name: "command" }, { name: "end" }, + { name: "do" }, + { name: "command" }, + { name: "while" }, + { name: "end" }, + { name: "do" }, + { name: "command" }, + { name: "repeatIf" }, + { name: "end" }, { name: "end" } ]); let playbackTree = _playbackTree.preprocess(); - expect(playbackTree[0].level).toEqual(0); // if - expect(playbackTree[1].level).toEqual(1); // command - expect(playbackTree[2].level).toEqual(0); // else - expect(playbackTree[3].level).toEqual(1); // while - expect(playbackTree[4].level).toEqual(2); // command - expect(playbackTree[5].level).toEqual(1); // end - expect(playbackTree[6].level).toEqual(0); // end + expect(playbackTree[0].level).toEqual(0); // if + expect(playbackTree[1].level).toEqual(1); // command + expect(playbackTree[2].level).toEqual(0); // else + expect(playbackTree[3].level).toEqual(1); // while + expect(playbackTree[4].level).toEqual(2); // command + expect(playbackTree[5].level).toEqual(1); // end + expect(playbackTree[6].level).toEqual(1); // do + expect(playbackTree[7].level).toEqual(2); // command + expect(playbackTree[8].level).toEqual(2); // while + expect(playbackTree[9].level).toEqual(1); // end + expect(playbackTree[10].level).toEqual(1); // do + expect(playbackTree[11].level).toEqual(2); // command + expect(playbackTree[12].level).toEqual(2); // repeatIf + expect(playbackTree[13].level).toEqual(1); // end + expect(playbackTree[14].level).toEqual(0); // end }); describe("Validation", () => { test("if, end", () => { @@ -288,12 +346,12 @@ describe("Control Flow", () => { let playbackTree = new PlaybackTree([{ name: "times" }, { name: "end" }]); expect(playbackTree.preprocess()).toBeTruthy(); }); - test("repeatIf, end", () => { - let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "end" }]); + test("do, repeatIf, end", () => { + let playbackTree = new PlaybackTree([{ name: "do" }, { name: "repeatIf" }, { name: "end" }]); expect(playbackTree.preprocess()).toBeTruthy(); }); - test("repeatIf, if, end, end", () => { - let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "if" }, { name: "end" }, { name: "end" }]); + test("do, while, end", () => { + let playbackTree = new PlaybackTree([{ name: "do" }, { name: "while" }, { name: "end" }]); expect(playbackTree.preprocess()).toBeTruthy(); }); }); @@ -336,41 +394,17 @@ describe("Control Flow", () => { }); test("repeatIf", () => { let playbackTree = new PlaybackTree([{ name: "repeatIf" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at repeatIf"); + expect(function() { playbackTree.preprocess(); }).toThrow("A repeatIf used without a do block"); }); - test("repeatIf, if", () => { - let playbackTree = new PlaybackTree([{ name: "repeatIf" }, { name: "if" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); + test("do", () => { + let playbackTree = new PlaybackTree([{ name: "do" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at do"); }); - }); - }); - - describe.skip("Process", () => { - describe("Generate Linked List", () => { - test("has next for command-command", () => { - let input = [ - { name: "command1" }, - { name: "command2" } - ]; - let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - playbackTree.process(); - expect(playbackTree.stack[0].next).toEqual(input[1]); - }); - - test("has next and left, right for if-command-end", () => { - let input = [ - { name: "if" }, - { name: "command" }, - { name: "end" } - ]; - let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - playbackTree.process(); - expect(playbackTree.stack[0].next).toBeUndefined(); - expect(playbackTree.stack[0].right).toEqual(input[1]); - expect(playbackTree.stack[0].left).toEqual(input[2]); + test("end", () => { + let playbackTree = new PlaybackTree([{ name: "end" }]); + expect(function() { playbackTree.preprocess(); }).toThrow("Use of end without an opening keyword"); }); }); }); + }); From 9fc151537615848edace9667a492523ab127dc4f Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 2 Jul 2018 23:10:42 -0400 Subject: [PATCH 006/125] WIP on linked list object creation. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 443 +++++++++++++----- 1 file changed, 326 insertions(+), 117 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 78de59da4..317937d3c 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -17,6 +17,11 @@ class Command { constructor(command, commandStackHandler) { + this.command = command; + this.commandStackHandler = commandStackHandler; + } + + static load(command, commandStackHandler) { switch(command.name) { case "if": return new If(command, commandStackHandler); @@ -24,22 +29,41 @@ class Command { case "elseIf": return new Else(command, commandStackHandler); case "while": + return new While(command, commandStackHandler); case "times": + return new Times(command, commandStackHandler); case "repeatIf": + return new RepeatIf(command, commandStackHandler); case "do": - return new Loop(command, commandStackHandler); + return new Do(command, commandStackHandler); case "end": return new End(command, commandStackHandler); default: return new Default(command, commandStackHandler); } } + + static isControlFlowCommand(name) { + return !!(["if", "else", "elseIf", "do", + "times", "while", "repeatIf", "end"].find(n => n === name)); + } + + static isLoop(name) { + return !!(["times", "while", "repeatIf"].find(n => n === name)); + } + + static isIf(name) { + return !!(["if"].find(n => n === name)); + } + + static isEnd(name) { + return !!(["end"].find(n => n === name)); + } } -class If { +class If extends Command { constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + super(command, commandStackHandler); } preprocess() { @@ -49,10 +73,9 @@ class If { } } -class Else { +class Else extends Command { constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + super(command, commandStackHandler); } preprocess() { @@ -65,20 +88,13 @@ class Else { } } -class Loop { +class While extends Command { constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + super(command, commandStackHandler); } preprocess() { - if (this.command.name === "repeatIf") { - if (this.commandStackHandler.top().name !== "do") { - throw "A repeatIf used without a do block"; - } - this.commandStackHandler.store(); - } else if (this.command.name === "while" && - this.commandStackHandler.top().name === "do") { + if (this.commandStackHandler.top().name === "do") { this.commandStackHandler.store(); } else { this.commandStackHandler.store(); @@ -86,13 +102,48 @@ class Loop { this.commandStackHandler.increaseLevel(); } } +} +class Do extends Command { + constructor(command, commandStackHandler) { + super(command, commandStackHandler); + } + + preprocess() { + this.commandStackHandler.store(); + this.commandStackHandler.setState(); + this.commandStackHandler.increaseLevel(); + } } -class End { +class Times extends Command { constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + super(command, commandStackHandler); + } + + preprocess() { + this.commandStackHandler.store(); + this.commandStackHandler.setState(); + this.commandStackHandler.increaseLevel(); + } +} + +class RepeatIf extends Command { + constructor(command, commandStackHandler) { + super(command, commandStackHandler); + } + + preprocess() { + if (this.commandStackHandler.top().name !== "do") { + throw "A repeatIf used without a do block"; + } + this.commandStackHandler.store(); + } +} + +class End extends Command { + constructor(command, commandStackHandler) { + super(command, commandStackHandler); } preprocess() { @@ -129,10 +180,9 @@ class End { } -class Default { +class Default extends Command { constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + super(command, commandStackHandler); } preprocess() { @@ -142,8 +192,8 @@ class Default { class CommandStackHandler { constructor(stack) { + this._stack = stack; this.stack = []; - Object.assign(this.stack, stack); this.state = []; this.level = 0; this.currentCommand; @@ -159,7 +209,10 @@ class CommandStackHandler { } store() { - Object.assign(this.stack[this.currentCommandIndex], { level: this.level }); + let node = new CommandNode; + node.command = this.currentCommand; + node.level = this.level; + this.stack.push(node); } setState() { @@ -184,7 +237,7 @@ class CommandStackHandler { } currentSegment() { - return this.stack.slice(this.top().index, this.currentCommandIndex); + return this._stack.slice(this.top().index, this.currentCommandIndex); } setCurrentCommand(command, index) { @@ -192,7 +245,7 @@ class CommandStackHandler { this.currentCommandIndex = index; } - confirmation() { + confirm() { if (this.state.length > 0) { throw "Incomplete block at " + this.top().name; } @@ -201,9 +254,12 @@ class CommandStackHandler { class CommandNode { constructor() { + this.command; this.next = undefined; this.left = undefined; this.right = undefined; + this.level; + this.timesVisited; } } @@ -211,117 +267,116 @@ class PlaybackTree { constructor(stack) { this.inputStack = stack; this.preprocessStack = []; - this.stack = []; + this.processStack = []; } preprocess() { let commandStackHandler = new CommandStackHandler(this.inputStack); this.inputStack.forEach(function(currentCommand, currentCommandIndex) { commandStackHandler.setCurrentCommand(currentCommand, currentCommandIndex); - let command = new Command(currentCommand, commandStackHandler); + let command = Command.load(currentCommand, commandStackHandler); command.preprocess(); }); - commandStackHandler.confirmation(); + commandStackHandler.confirm(); this.preprocessStack = commandStackHandler.stack; return this.preprocessStack; } + nextNodeAtSameLevel(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].level === level) { + return stack[i]; + } + } + } + + nextEndNode(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].command.name === "end" && stack[i].level === level) { + return stack[i]; + } + } + } + + previousOpeningNode(stack, index, level) { + for(let i = index; i > -1; i--) { + if (stack[i].level === level - 1) { + return stack[i]; + } + } + } + process() { - //let _stack = this.preprocessStack; - //let stack = this.stack; - //this.preprocessStack.forEach(function(command, index) { - // if (_stack[index + 1] && (command.level === _stack[index + 1].level)) { - // let node = new CommandNode; - // node.next = _stack[index + 1]; - // stack.push(node); - // } else { - // let node = new CommandNode; - // node.right = _stack[index + 1]; - // let segment = _stack.slice(index + 1, _stack.length + 1); - // let leftTarget = segment.findIndex(kommand => kommand.level === command.level); - // for(let i = index; i < _stack.length + 1; i++) { - // if (_stack[i + 1] && (_stack[i].level === _stack[i + 1].level)) { - // node.left = _stack[i + 1]; - // break; - // } - // } - // stack.push(node); - // } - //}); - } - - topOf(stack) { - return stack[stack.length - 1]; + let _stack = this.preprocessStack; + let stack = this.processStack; + let nextNodeAtSameLevel = this.nextNodeAtSameLevel; + let nextEndNode = this.nextEndNode; + let previousOpeningNode = this.previousOpeningNode; + _stack.forEach(function(currentCommandNode, index) { + let nextCommandNode = _stack[index + 1]; + if (nextCommandNode) { + if (Command.isControlFlowCommand(currentCommandNode.command.name) && + !Command.isEnd(currentCommandNode.command.name)) { + currentCommandNode.right = nextCommandNode; + currentCommandNode.left = nextNodeAtSameLevel(_stack, index, currentCommandNode.level); + } else if (Command.isControlFlowCommand(nextCommandNode.command.name)) { + let openingNode; + openingNode = previousOpeningNode(_stack, index, currentCommandNode.level); + if (openingNode && !Command.isLoop(openingNode.command.name)) { + currentCommandNode.next = nextEndNode(_stack, index, openingNode.level); + } else { + currentCommandNode.next = openingNode; + } + } else { + currentCommandNode.next = nextCommandNode; + } + } + stack.push(currentCommandNode); + }); } } describe("Control Flow", () => { - describe.skip("Process", () => { - describe("Generate Linked List", () => { - test("has next for command-command", () => { - let input = [ - { name: "command1" }, - { name: "command2" } - ]; - let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - playbackTree.process(); - expect(playbackTree.stack[0].next).toEqual(input[1]); - }); - - test("has next and left, right for if-command-end", () => { - let input = [ + describe("Preprocess", () => { + describe("Leveling", () => { + test("returns leveled command stack", () => { + let _playbackTree = new PlaybackTree([ { name: "if" }, { name: "command" }, + { name: "else" }, + { name: "while" }, + { name: "command" }, + { name: "end" }, + { name: "do" }, + { name: "command" }, + { name: "while" }, + { name: "end" }, + { name: "do" }, + { name: "command" }, + { name: "repeatIf" }, + { name: "end" }, { name: "end" } - ]; - let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - playbackTree.process(); - expect(playbackTree.stack[0].next).toBeUndefined(); - expect(playbackTree.stack[0].right).toEqual(input[1]); - expect(playbackTree.stack[0].left).toEqual(input[2]); + ]); + let playbackTree = _playbackTree.preprocess(); + expect(playbackTree[0].level).toEqual(0); // if + expect(playbackTree[1].level).toEqual(1); // command + expect(playbackTree[2].level).toEqual(0); // else + expect(playbackTree[3].level).toEqual(1); // while + expect(playbackTree[4].level).toEqual(2); // command + expect(playbackTree[5].level).toEqual(1); // end + expect(playbackTree[6].level).toEqual(1); // do + expect(playbackTree[7].level).toEqual(2); // command + expect(playbackTree[8].level).toEqual(2); // while + expect(playbackTree[9].level).toEqual(1); // end + expect(playbackTree[10].level).toEqual(1); // do + expect(playbackTree[11].level).toEqual(2); // command + expect(playbackTree[12].level).toEqual(2); // repeatIf + expect(playbackTree[13].level).toEqual(1); // end + expect(playbackTree[14].level).toEqual(0); // end }); }); - }); - describe("Preprocess", () => { - test("marked with correct levels", () => { - let _playbackTree = new PlaybackTree([ - { name: "if" }, - { name: "command" }, - { name: "else" }, - { name: "while" }, - { name: "command" }, - { name: "end" }, - { name: "do" }, - { name: "command" }, - { name: "while" }, - { name: "end" }, - { name: "do" }, - { name: "command" }, - { name: "repeatIf" }, - { name: "end" }, - { name: "end" } - ]); - let playbackTree = _playbackTree.preprocess(); - expect(playbackTree[0].level).toEqual(0); // if - expect(playbackTree[1].level).toEqual(1); // command - expect(playbackTree[2].level).toEqual(0); // else - expect(playbackTree[3].level).toEqual(1); // while - expect(playbackTree[4].level).toEqual(2); // command - expect(playbackTree[5].level).toEqual(1); // end - expect(playbackTree[6].level).toEqual(1); // do - expect(playbackTree[7].level).toEqual(2); // command - expect(playbackTree[8].level).toEqual(2); // while - expect(playbackTree[9].level).toEqual(1); // end - expect(playbackTree[10].level).toEqual(1); // do - expect(playbackTree[11].level).toEqual(2); // command - expect(playbackTree[12].level).toEqual(2); // repeatIf - expect(playbackTree[13].level).toEqual(1); // end - expect(playbackTree[14].level).toEqual(0); // end - }); - describe("Validation", () => { + describe("Syntax Validation", () => { test("if, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "end" }]); expect(playbackTree.preprocess()).toBeTruthy(); @@ -355,7 +410,7 @@ describe("Control Flow", () => { expect(playbackTree.preprocess()).toBeTruthy(); }); }); - describe("Invalidation", () => { + describe("Syntax Invalidation", () => { test("if", () => { let playbackTree = new PlaybackTree([{ name: "if" }]); expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); @@ -406,5 +461,159 @@ describe("Control Flow", () => { }); }); }); - + describe("Process", () => { + describe("Linked List Validation", () => { + test("command-command", () => { + let input = [ + { name: "command1" }, + { name: "command2" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + expect(playbackTree.processStack[0].command).toEqual(input[0]); + expect(playbackTree.processStack[1].command).toEqual(input[1]); + expect(playbackTree.processStack[0].next).toEqual(playbackTree.processStack[1]); + expect(playbackTree.processStack[0].left).toBeUndefined(); + expect(playbackTree.processStack[0].right).toBeUndefined(); + expect(playbackTree.processStack[1].next).toBeUndefined(); + expect(playbackTree.processStack[1].left).toBeUndefined(); + expect(playbackTree.processStack[1].right).toBeUndefined(); + }); + test("if-command-else-command-end", () => { + let input = [ + { name: "if" }, + { name: "command" }, + { name: "else" }, + { name: "command" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + let stack = playbackTree.processStack; + expect(stack[0].command).toEqual(input[0]); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[2]); + expect(stack[1].command).toEqual(input[1]); + expect(stack[1].next).toEqual(stack[4]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].command).toEqual(input[2]); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[3]); + expect(stack[2].left).toEqual(stack[4]); + expect(stack[3].command).toEqual(input[3]); + expect(stack[3].next).toEqual(stack[4]); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + expect(stack[4].command).toEqual(input[4]); + expect(stack[4].next).toBeUndefined(); + expect(stack[4].right).toBeUndefined(); + expect(stack[4].left).toBeUndefined(); + }); + test("while-command-end", () => { + let input = [ + { name: "while" }, + { name: "command" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + let stack = playbackTree.processStack; + expect(stack[0].command).toEqual(input[0]); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[2]); + expect(stack[1].command).toEqual(input[1]); + expect(stack[1].next).toEqual(stack[0]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].command).toEqual(input[2]); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toBeUndefined(); + expect(stack[2].left).toBeUndefined(); + }); + test("if-while-command-end-else-command-end", () => { + let input = [ + { name: "if" }, + { name: "while" }, + { name: "command" }, + { name: "end" }, + { name: "else" }, + { name: "command" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + let stack = playbackTree.processStack; + // if + expect(stack[0].command).toEqual(input[0]); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[4]); + // while + expect(stack[1].command).toEqual(input[1]); + expect(stack[1].next).toBeUndefined(); + expect(stack[1].right).toEqual(stack[2]); + expect(stack[1].left).toEqual(stack[3]); + // command + expect(stack[2].command).toEqual(input[2]); + expect(stack[2].next).toEqual(stack[1]); + expect(stack[2].right).toBeUndefined(); + expect(stack[2].left).toBeUndefined(); + // end + expect(stack[3].command).toEqual(input[3]); + expect(stack[3].next).toEqual(stack[6]); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + // else + expect(stack[4].command).toEqual(input[4]); + expect(stack[4].next).toBeUndefined(); + expect(stack[4].right).toEqual(stack[5]); + expect(stack[4].left).toEqual(stack[6]); + // command + expect(stack[5].command).toEqual(input[5]); + expect(stack[5].next).toEqual(stack[6]); + expect(stack[5].right).toBeUndefined(); + expect(stack[5].left).toBeUndefined(); + // end + expect(stack[6].command).toEqual(input[6]); + expect(stack[6].next).toBeUndefined(); + expect(stack[6].right).toBeUndefined(); + expect(stack[6].left).toBeUndefined(); + }); + test.skip("do-command-while-end", () => { + let input = [ + { name: "do" }, + { name: "command" }, + { name: "while" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree.preprocess(); + playbackTree.process(); + let stack = playbackTree.processStack; + expect(stack[0].command).toEqual(input[0]); + expect(stack[0].next).toEqual(stack[1]); + expect(stack[0].right).toBeUndefined(); + expect(stack[0].left).toBeUndefined(); + expect(stack[1].command).toEqual(input[1]); + expect(stack[1].next).toEqual(stack[2]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].command).toEqual(input[2]); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[0]); + expect(stack[2].left).toEqual(stack[3]); + expect(stack[3].command).toEqual(input[3]); + expect(stack[3].next).toBeUndefined(); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + }); + }); + }); }); From 30118730b8fff95394e885c5bad971eea0b1ba7e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 10:16:30 -0400 Subject: [PATCH 007/125] Additional bits for linked list creation, and cleaned up the hard dependency between Command and CommandStackHandler. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 191 +++++++++--------- 1 file changed, 95 insertions(+), 96 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 317937d3c..594631969 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -16,177 +16,166 @@ // under the License. class Command { - constructor(command, commandStackHandler) { - this.command = command; - this.commandStackHandler = commandStackHandler; + constructor(command) { + Object.assign(this, command); } - static load(command, commandStackHandler) { + static load(command) { switch(command.name) { case "if": - return new If(command, commandStackHandler); + return new If(command); case "else": case "elseIf": - return new Else(command, commandStackHandler); + return new Else(command); case "while": - return new While(command, commandStackHandler); + return new While(command); case "times": - return new Times(command, commandStackHandler); + return new Times(command); case "repeatIf": - return new RepeatIf(command, commandStackHandler); + return new RepeatIf(command); case "do": - return new Do(command, commandStackHandler); + return new Do(command); case "end": - return new End(command, commandStackHandler); + return new End(command); default: - return new Default(command, commandStackHandler); + return new Default(command); } } - static isControlFlowCommand(name) { + isControlFlowCommand() { return !!(["if", "else", "elseIf", "do", - "times", "while", "repeatIf", "end"].find(n => n === name)); + "times", "while", "repeatIf", "end"].find(n => n === this.name)); } - static isLoop(name) { - return !!(["times", "while", "repeatIf"].find(n => n === name)); + isLoop() { + return !!(["times", "while", "repeatIf"].find(n => n === this.name)); } - static isIf(name) { - return !!(["if"].find(n => n === name)); + isIf() { + return !!(["if"].find(n => n === this.name)); } - static isEnd(name) { - return !!(["end"].find(n => n === name)); + isEnd() { + return !!(["end"].find(n => n === this.name)); } } class If extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - this.commandStackHandler.store(); - this.commandStackHandler.setState(); - this.commandStackHandler.increaseLevel(); + preprocess(commandStackHandler) { + commandStackHandler.store(); + commandStackHandler.setState(); + commandStackHandler.increaseLevel(); } } class Else extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - if (this.commandStackHandler.top().name !== "if") { + preprocess(commandStackHandler) { + if (commandStackHandler.top().name !== "if") { throw "An else / elseIf used outside of an if block"; } - this.commandStackHandler.decreaseLevel(); - this.commandStackHandler.store(); - this.commandStackHandler.increaseLevel(); + commandStackHandler.decreaseLevel(); + commandStackHandler.store(); + commandStackHandler.increaseLevel(); } } class While extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - if (this.commandStackHandler.top().name === "do") { - this.commandStackHandler.store(); + preprocess(commandStackHandler) { + if (commandStackHandler.top().name === "do") { + commandStackHandler.store(); } else { - this.commandStackHandler.store(); - this.commandStackHandler.setState(); - this.commandStackHandler.increaseLevel(); + commandStackHandler.store(); + commandStackHandler.setState(); + commandStackHandler.increaseLevel(); } } } class Do extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - this.commandStackHandler.store(); - this.commandStackHandler.setState(); - this.commandStackHandler.increaseLevel(); + preprocess(commandStackHandler) { + commandStackHandler.store(); + commandStackHandler.setState(); + commandStackHandler.increaseLevel(); } } class Times extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - this.commandStackHandler.store(); - this.commandStackHandler.setState(); - this.commandStackHandler.increaseLevel(); + preprocess(commandStackHandler) { + commandStackHandler.store(); + commandStackHandler.setState(); + commandStackHandler.increaseLevel(); } } class RepeatIf extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - if (this.commandStackHandler.top().name !== "do") { + preprocess(commandStackHandler) { + if (commandStackHandler.top().name !== "do") { throw "A repeatIf used without a do block"; } - this.commandStackHandler.store(); + commandStackHandler.store(); } } class End extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); - } - - preprocess() { - if (this.terminatesLoop()) { - this.commandStackHandler.decreaseLevel(); - this.commandStackHandler.store(); - this.commandStackHandler.popState(); - } else if (this.terminatesIf()) { - const elseCount = this.commandStackHandler.currentSegment().filter(command => command.name === "else").length; - const elseSegment = this.commandStackHandler.currentSegment().filter(command => command.name.match(/else/)); + constructor(command) { + super(command); + } + + preprocess(commandStackHandler) { + if (commandStackHandler.terminatesLoop()) { + commandStackHandler.decreaseLevel(); + commandStackHandler.store(); + commandStackHandler.popState(); + } else if (commandStackHandler.terminatesIf()) { + const elseCount = commandStackHandler.currentSegment().filter(command => command.name === "else").length; + const elseSegment = commandStackHandler.currentSegment().filter(command => command.name.match(/else/)); if (elseCount > 1) { throw "Too many else commands used"; - } else if (elseCount === 1 && this.commandStackHandler.topOf(elseSegment).name !== "else") { + } else if (elseCount === 1 && commandStackHandler.topOf(elseSegment).name !== "else") { throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || this.commandStackHandler.topOf(elseSegment).name === "else") { - this.commandStackHandler.decreaseLevel(); - this.commandStackHandler.store(); - this.commandStackHandler.popState(); + } else if (elseCount === 0 || commandStackHandler.topOf(elseSegment).name === "else") { + commandStackHandler.decreaseLevel(); + commandStackHandler.store(); + commandStackHandler.popState(); } } else { throw "Use of end without an opening keyword"; } } - terminatesIf() { - return (this.commandStackHandler.top().name === "if"); - } - - terminatesLoop() { - return (this.commandStackHandler.top().name === "while" || - this.commandStackHandler.top().name === "times" || - this.commandStackHandler.top().name === "do"); - } - } class Default extends Command { - constructor(command, commandStackHandler) { - super(command, commandStackHandler); + constructor(command) { + super(command); } - preprocess() { - this.commandStackHandler.store(); + preprocess(commandStackHandler) { + commandStackHandler.store(); } } @@ -236,6 +225,16 @@ class CommandStackHandler { return segment[segment.length - 1]; } + terminatesIf() { + return (this.top().name === "if"); + } + + terminatesLoop() { + return (this.top().name === "while" || + this.top().name === "times" || + this.top().name === "do"); + } + currentSegment() { return this._stack.slice(this.top().index, this.currentCommandIndex); } @@ -273,9 +272,9 @@ class PlaybackTree { preprocess() { let commandStackHandler = new CommandStackHandler(this.inputStack); this.inputStack.forEach(function(currentCommand, currentCommandIndex) { - commandStackHandler.setCurrentCommand(currentCommand, currentCommandIndex); - let command = Command.load(currentCommand, commandStackHandler); - command.preprocess(); + let command = Command.load(currentCommand); + commandStackHandler.setCurrentCommand(command, currentCommandIndex); + command.preprocess(commandStackHandler); }); commandStackHandler.confirm(); this.preprocessStack = commandStackHandler.stack; @@ -315,14 +314,14 @@ class PlaybackTree { _stack.forEach(function(currentCommandNode, index) { let nextCommandNode = _stack[index + 1]; if (nextCommandNode) { - if (Command.isControlFlowCommand(currentCommandNode.command.name) && - !Command.isEnd(currentCommandNode.command.name)) { + if (currentCommandNode.command.isControlFlowCommand() && + !currentCommandNode.command.isEnd()) { currentCommandNode.right = nextCommandNode; currentCommandNode.left = nextNodeAtSameLevel(_stack, index, currentCommandNode.level); - } else if (Command.isControlFlowCommand(nextCommandNode.command.name)) { + } else if (nextCommandNode.command.isControlFlowCommand()) { let openingNode; openingNode = previousOpeningNode(_stack, index, currentCommandNode.level); - if (openingNode && !Command.isLoop(openingNode.command.name)) { + if (openingNode && !openingNode.command.isLoop()) { currentCommandNode.next = nextEndNode(_stack, index, openingNode.level); } else { currentCommandNode.next = openingNode; From 355e05eb788de534a5e5b23723e6a66062df7d47 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 13:23:27 -0400 Subject: [PATCH 008/125] Made it so #preprocess and #process return their respective collection after storing them. Updated tests to use the return value instead of relying on the variable name since the implementation could change. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 89 +++++++++---------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 594631969..0e349e17b 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -265,8 +265,8 @@ class CommandNode { class PlaybackTree { constructor(stack) { this.inputStack = stack; - this.preprocessStack = []; - this.processStack = []; + this._preprocessStack = []; + this._processStack = []; } preprocess() { @@ -277,8 +277,8 @@ class PlaybackTree { command.preprocess(commandStackHandler); }); commandStackHandler.confirm(); - this.preprocessStack = commandStackHandler.stack; - return this.preprocessStack; + Object.assign(this._preprocessStack, commandStackHandler.stack); + return this._preprocessStack; } nextNodeAtSameLevel(stack, index, level) { @@ -306,23 +306,23 @@ class PlaybackTree { } process() { - let _stack = this.preprocessStack; - let stack = this.processStack; + let _preprocessStack = this._preprocessStack; + let _processStack = this._processStack; let nextNodeAtSameLevel = this.nextNodeAtSameLevel; let nextEndNode = this.nextEndNode; let previousOpeningNode = this.previousOpeningNode; - _stack.forEach(function(currentCommandNode, index) { - let nextCommandNode = _stack[index + 1]; + _preprocessStack.forEach(function(currentCommandNode, index) { + let nextCommandNode = _preprocessStack[index + 1]; if (nextCommandNode) { if (currentCommandNode.command.isControlFlowCommand() && !currentCommandNode.command.isEnd()) { currentCommandNode.right = nextCommandNode; - currentCommandNode.left = nextNodeAtSameLevel(_stack, index, currentCommandNode.level); + currentCommandNode.left = nextNodeAtSameLevel(_preprocessStack, index, currentCommandNode.level); } else if (nextCommandNode.command.isControlFlowCommand()) { let openingNode; - openingNode = previousOpeningNode(_stack, index, currentCommandNode.level); + openingNode = previousOpeningNode(_preprocessStack, index, currentCommandNode.level); if (openingNode && !openingNode.command.isLoop()) { - currentCommandNode.next = nextEndNode(_stack, index, openingNode.level); + currentCommandNode.next = nextEndNode(_preprocessStack, index, openingNode.level); } else { currentCommandNode.next = openingNode; } @@ -330,8 +330,9 @@ class PlaybackTree { currentCommandNode.next = nextCommandNode; } } - stack.push(currentCommandNode); + _processStack.push(currentCommandNode); }); + return this._processStack; } } @@ -340,7 +341,7 @@ describe("Control Flow", () => { describe("Preprocess", () => { describe("Leveling", () => { test("returns leveled command stack", () => { - let _playbackTree = new PlaybackTree([ + let playbackTree = new PlaybackTree([ { name: "if" }, { name: "command" }, { name: "else" }, @@ -357,22 +358,22 @@ describe("Control Flow", () => { { name: "end" }, { name: "end" } ]); - let playbackTree = _playbackTree.preprocess(); - expect(playbackTree[0].level).toEqual(0); // if - expect(playbackTree[1].level).toEqual(1); // command - expect(playbackTree[2].level).toEqual(0); // else - expect(playbackTree[3].level).toEqual(1); // while - expect(playbackTree[4].level).toEqual(2); // command - expect(playbackTree[5].level).toEqual(1); // end - expect(playbackTree[6].level).toEqual(1); // do - expect(playbackTree[7].level).toEqual(2); // command - expect(playbackTree[8].level).toEqual(2); // while - expect(playbackTree[9].level).toEqual(1); // end - expect(playbackTree[10].level).toEqual(1); // do - expect(playbackTree[11].level).toEqual(2); // command - expect(playbackTree[12].level).toEqual(2); // repeatIf - expect(playbackTree[13].level).toEqual(1); // end - expect(playbackTree[14].level).toEqual(0); // end + let stack = playbackTree.preprocess(); + expect(stack[0].level).toEqual(0); // if + expect(stack[1].level).toEqual(1); // command + expect(stack[2].level).toEqual(0); // else + expect(stack[3].level).toEqual(1); // while + expect(stack[4].level).toEqual(2); // command + expect(stack[5].level).toEqual(1); // end + expect(stack[6].level).toEqual(1); // do + expect(stack[7].level).toEqual(2); // command + expect(stack[8].level).toEqual(2); // while + expect(stack[9].level).toEqual(1); // end + expect(stack[10].level).toEqual(1); // do + expect(stack[11].level).toEqual(2); // command + expect(stack[12].level).toEqual(2); // repeatIf + expect(stack[13].level).toEqual(1); // end + expect(stack[14].level).toEqual(0); // end }); }); describe("Syntax Validation", () => { @@ -469,15 +470,15 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree.preprocess(); - playbackTree.process(); - expect(playbackTree.processStack[0].command).toEqual(input[0]); - expect(playbackTree.processStack[1].command).toEqual(input[1]); - expect(playbackTree.processStack[0].next).toEqual(playbackTree.processStack[1]); - expect(playbackTree.processStack[0].left).toBeUndefined(); - expect(playbackTree.processStack[0].right).toBeUndefined(); - expect(playbackTree.processStack[1].next).toBeUndefined(); - expect(playbackTree.processStack[1].left).toBeUndefined(); - expect(playbackTree.processStack[1].right).toBeUndefined(); + let stack = playbackTree.process(); + expect(stack[0].command).toEqual(input[0]); + expect(stack[1].command).toEqual(input[1]); + expect(stack[0].next).toEqual(stack[1]); + expect(stack[0].left).toBeUndefined(); + expect(stack[0].right).toBeUndefined(); + expect(stack[1].next).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[1].right).toBeUndefined(); }); test("if-command-else-command-end", () => { let input = [ @@ -489,8 +490,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree.preprocess(); - playbackTree.process(); - let stack = playbackTree.processStack; + let stack = playbackTree.process(); expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -520,8 +520,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree.preprocess(); - playbackTree.process(); - let stack = playbackTree.processStack; + let stack = playbackTree.process(); expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -547,8 +546,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree.preprocess(); - playbackTree.process(); - let stack = playbackTree.processStack; + let stack = playbackTree.process(); // if expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); @@ -594,8 +592,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree.preprocess(); - playbackTree.process(); - let stack = playbackTree.processStack; + let stack = playbackTree.process(); expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); From 8b1b27037e5b2eb91a9b91f916e9e000ad93fb68 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 13:31:55 -0400 Subject: [PATCH 009/125] Renamed _processStack to stack. Also cleaned up #process to clone _preprocessStack into stack and work with just stack. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 0e349e17b..58bbb8813 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -266,7 +266,7 @@ class PlaybackTree { constructor(stack) { this.inputStack = stack; this._preprocessStack = []; - this._processStack = []; + this.stack = []; } preprocess() { @@ -306,23 +306,23 @@ class PlaybackTree { } process() { - let _preprocessStack = this._preprocessStack; - let _processStack = this._processStack; + Object.assign(this.stack, this._preprocessStack); + let stack = this.stack; let nextNodeAtSameLevel = this.nextNodeAtSameLevel; let nextEndNode = this.nextEndNode; let previousOpeningNode = this.previousOpeningNode; - _preprocessStack.forEach(function(currentCommandNode, index) { - let nextCommandNode = _preprocessStack[index + 1]; + stack.forEach(function(currentCommandNode, currentCommandIndex) { + let nextCommandNode = stack[currentCommandIndex + 1]; if (nextCommandNode) { if (currentCommandNode.command.isControlFlowCommand() && !currentCommandNode.command.isEnd()) { currentCommandNode.right = nextCommandNode; - currentCommandNode.left = nextNodeAtSameLevel(_preprocessStack, index, currentCommandNode.level); + currentCommandNode.left = nextNodeAtSameLevel(stack, currentCommandIndex, currentCommandNode.level); } else if (nextCommandNode.command.isControlFlowCommand()) { let openingNode; - openingNode = previousOpeningNode(_preprocessStack, index, currentCommandNode.level); + openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); if (openingNode && !openingNode.command.isLoop()) { - currentCommandNode.next = nextEndNode(_preprocessStack, index, openingNode.level); + currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); } else { currentCommandNode.next = openingNode; } @@ -330,9 +330,8 @@ class PlaybackTree { currentCommandNode.next = nextCommandNode; } } - _processStack.push(currentCommandNode); }); - return this._processStack; + return this.stack; } } From 7e342fa458146774c363c1d6c63b7b463ded9b78 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 13:41:30 -0400 Subject: [PATCH 010/125] Cleaned up CommandStackHandler to use better variable and method naming. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 76 +++++++++---------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 58bbb8813..6f56e3049 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -66,8 +66,8 @@ class If extends Command { } preprocess(commandStackHandler) { - commandStackHandler.store(); - commandStackHandler.setState(); + commandStackHandler.createAndStoreCommandNode(); + commandStackHandler.pushState(); commandStackHandler.increaseLevel(); } } @@ -78,11 +78,11 @@ class Else extends Command { } preprocess(commandStackHandler) { - if (commandStackHandler.top().name !== "if") { + if (commandStackHandler.topOfState().name !== "if") { throw "An else / elseIf used outside of an if block"; } commandStackHandler.decreaseLevel(); - commandStackHandler.store(); + commandStackHandler.createAndStoreCommandNode(); commandStackHandler.increaseLevel(); } } @@ -93,11 +93,11 @@ class While extends Command { } preprocess(commandStackHandler) { - if (commandStackHandler.top().name === "do") { - commandStackHandler.store(); + if (commandStackHandler.topOfState().name === "do") { + commandStackHandler.createAndStoreCommandNode(); } else { - commandStackHandler.store(); - commandStackHandler.setState(); + commandStackHandler.createAndStoreCommandNode(); + commandStackHandler.pushState(); commandStackHandler.increaseLevel(); } } @@ -109,8 +109,8 @@ class Do extends Command { } preprocess(commandStackHandler) { - commandStackHandler.store(); - commandStackHandler.setState(); + commandStackHandler.createAndStoreCommandNode(); + commandStackHandler.pushState(); commandStackHandler.increaseLevel(); } } @@ -121,8 +121,8 @@ class Times extends Command { } preprocess(commandStackHandler) { - commandStackHandler.store(); - commandStackHandler.setState(); + commandStackHandler.createAndStoreCommandNode(); + commandStackHandler.pushState(); commandStackHandler.increaseLevel(); } } @@ -133,10 +133,10 @@ class RepeatIf extends Command { } preprocess(commandStackHandler) { - if (commandStackHandler.top().name !== "do") { + if (commandStackHandler.topOfState().name !== "do") { throw "A repeatIf used without a do block"; } - commandStackHandler.store(); + commandStackHandler.createAndStoreCommandNode(); } } @@ -148,7 +148,7 @@ class End extends Command { preprocess(commandStackHandler) { if (commandStackHandler.terminatesLoop()) { commandStackHandler.decreaseLevel(); - commandStackHandler.store(); + commandStackHandler.createAndStoreCommandNode(); commandStackHandler.popState(); } else if (commandStackHandler.terminatesIf()) { const elseCount = commandStackHandler.currentSegment().filter(command => command.name === "else").length; @@ -159,7 +159,7 @@ class End extends Command { throw "Incorrect command order of elseIf / else"; } else if (elseCount === 0 || commandStackHandler.topOf(elseSegment).name === "else") { commandStackHandler.decreaseLevel(); - commandStackHandler.store(); + commandStackHandler.createAndStoreCommandNode(); commandStackHandler.popState(); } } else { @@ -175,18 +175,18 @@ class Default extends Command { } preprocess(commandStackHandler) { - commandStackHandler.store(); + commandStackHandler.createAndStoreCommandNode(); } } class CommandStackHandler { constructor(stack) { - this._stack = stack; + this._inputStack = stack; + this._state = []; + this._currentCommand; + this._currentCommandIndex; this.stack = []; - this.state = []; this.level = 0; - this.currentCommand; - this.currentCommandIndex; } increaseLevel() { @@ -197,25 +197,25 @@ class CommandStackHandler { this.level--; } - store() { + createAndStoreCommandNode() { let node = new CommandNode; - node.command = this.currentCommand; + node.command = this._currentCommand; node.level = this.level; this.stack.push(node); } - setState() { - this.state.push({ name: this.currentCommand.name, index: this.currentCommandIndex }); + pushState() { + this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); } popState() { - this.state.pop(); + this._state.pop(); } - top() { - let command = this.state[this.state.length - 1]; + topOfState() { + let command = this._state[this._state.length - 1]; if (command) { - return this.state[this.state.length - 1]; + return this._state[this._state.length - 1]; } else { return { name: "" }; } @@ -226,27 +226,27 @@ class CommandStackHandler { } terminatesIf() { - return (this.top().name === "if"); + return (this.topOfState().name === "if"); } terminatesLoop() { - return (this.top().name === "while" || - this.top().name === "times" || - this.top().name === "do"); + return (this.topOfState().name === "while" || + this.topOfState().name === "times" || + this.topOfState().name === "do"); } currentSegment() { - return this._stack.slice(this.top().index, this.currentCommandIndex); + return this._inputStack.slice(this.topOfState().index, this._currentCommandIndex); } setCurrentCommand(command, index) { - this.currentCommand = command; - this.currentCommandIndex = index; + this._currentCommand = command; + this._currentCommandIndex = index; } confirm() { - if (this.state.length > 0) { - throw "Incomplete block at " + this.top().name; + if (this._state.length > 0) { + throw "Incomplete block at " + this.topOfState().name; } } } From b9f0c8c4d534f728a10d631c0514a3e9d895b44e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 15:56:22 -0400 Subject: [PATCH 011/125] Moved setting the command and index on the CommandStackHandler into #preprocess for each Command. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 6f56e3049..cce957b81 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -65,7 +65,8 @@ class If extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); commandStackHandler.createAndStoreCommandNode(); commandStackHandler.pushState(); commandStackHandler.increaseLevel(); @@ -77,7 +78,8 @@ class Else extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); if (commandStackHandler.topOfState().name !== "if") { throw "An else / elseIf used outside of an if block"; } @@ -92,7 +94,8 @@ class While extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); if (commandStackHandler.topOfState().name === "do") { commandStackHandler.createAndStoreCommandNode(); } else { @@ -108,7 +111,8 @@ class Do extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); commandStackHandler.createAndStoreCommandNode(); commandStackHandler.pushState(); commandStackHandler.increaseLevel(); @@ -120,7 +124,8 @@ class Times extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); commandStackHandler.createAndStoreCommandNode(); commandStackHandler.pushState(); commandStackHandler.increaseLevel(); @@ -132,7 +137,8 @@ class RepeatIf extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); if (commandStackHandler.topOfState().name !== "do") { throw "A repeatIf used without a do block"; } @@ -145,7 +151,8 @@ class End extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); if (commandStackHandler.terminatesLoop()) { commandStackHandler.decreaseLevel(); commandStackHandler.createAndStoreCommandNode(); @@ -174,7 +181,8 @@ class Default extends Command { super(command); } - preprocess(commandStackHandler) { + preprocess(commandIndex, commandStackHandler) { + commandStackHandler.setCurrentCommand(this, commandIndex); commandStackHandler.createAndStoreCommandNode(); } } @@ -273,8 +281,7 @@ class PlaybackTree { let commandStackHandler = new CommandStackHandler(this.inputStack); this.inputStack.forEach(function(currentCommand, currentCommandIndex) { let command = Command.load(currentCommand); - commandStackHandler.setCurrentCommand(command, currentCommandIndex); - command.preprocess(commandStackHandler); + command.preprocess(currentCommandIndex, commandStackHandler); }); commandStackHandler.confirm(); Object.assign(this._preprocessStack, commandStackHandler.stack); From f06031ae47bd154dd5db4a1f4b3efdee975184c9 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 3 Jul 2018 16:00:52 -0400 Subject: [PATCH 012/125] Renamed #confirm to #confirmControlFlowSyntax --- .../selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index cce957b81..e9144c23c 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -252,7 +252,7 @@ class CommandStackHandler { this._currentCommandIndex = index; } - confirm() { + confirmControlFlowSyntax() { if (this._state.length > 0) { throw "Incomplete block at " + this.topOfState().name; } @@ -283,7 +283,7 @@ class PlaybackTree { let command = Command.load(currentCommand); command.preprocess(currentCommandIndex, commandStackHandler); }); - commandStackHandler.confirm(); + commandStackHandler.confirmControlFlowSyntax(); Object.assign(this._preprocessStack, commandStackHandler.stack); return this._preprocessStack; } From 378ea18a9dc5cbc1af124f747efec448de1f4063 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 4 Jul 2018 14:50:48 -0400 Subject: [PATCH 013/125] Moved away from the Command class hierarchy and towards a distilled set of mutation functions (names TBD) called from within CommandStackHandler. Also swapped out Object.assign for the spread operator when creating array copies. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 293 ++++++------------ 1 file changed, 89 insertions(+), 204 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index e9144c23c..1694201a3 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -15,178 +15,6 @@ // specific language governing permissions and limitations // under the License. -class Command { - constructor(command) { - Object.assign(this, command); - } - - static load(command) { - switch(command.name) { - case "if": - return new If(command); - case "else": - case "elseIf": - return new Else(command); - case "while": - return new While(command); - case "times": - return new Times(command); - case "repeatIf": - return new RepeatIf(command); - case "do": - return new Do(command); - case "end": - return new End(command); - default: - return new Default(command); - } - } - - isControlFlowCommand() { - return !!(["if", "else", "elseIf", "do", - "times", "while", "repeatIf", "end"].find(n => n === this.name)); - } - - isLoop() { - return !!(["times", "while", "repeatIf"].find(n => n === this.name)); - } - - isIf() { - return !!(["if"].find(n => n === this.name)); - } - - isEnd() { - return !!(["end"].find(n => n === this.name)); - } -} - -class If extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.pushState(); - commandStackHandler.increaseLevel(); - } -} - -class Else extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - if (commandStackHandler.topOfState().name !== "if") { - throw "An else / elseIf used outside of an if block"; - } - commandStackHandler.decreaseLevel(); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.increaseLevel(); - } -} - -class While extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - if (commandStackHandler.topOfState().name === "do") { - commandStackHandler.createAndStoreCommandNode(); - } else { - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.pushState(); - commandStackHandler.increaseLevel(); - } - } -} - -class Do extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.pushState(); - commandStackHandler.increaseLevel(); - } -} - -class Times extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.pushState(); - commandStackHandler.increaseLevel(); - } -} - -class RepeatIf extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - if (commandStackHandler.topOfState().name !== "do") { - throw "A repeatIf used without a do block"; - } - commandStackHandler.createAndStoreCommandNode(); - } -} - -class End extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - if (commandStackHandler.terminatesLoop()) { - commandStackHandler.decreaseLevel(); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.popState(); - } else if (commandStackHandler.terminatesIf()) { - const elseCount = commandStackHandler.currentSegment().filter(command => command.name === "else").length; - const elseSegment = commandStackHandler.currentSegment().filter(command => command.name.match(/else/)); - if (elseCount > 1) { - throw "Too many else commands used"; - } else if (elseCount === 1 && commandStackHandler.topOf(elseSegment).name !== "else") { - throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || commandStackHandler.topOf(elseSegment).name === "else") { - commandStackHandler.decreaseLevel(); - commandStackHandler.createAndStoreCommandNode(); - commandStackHandler.popState(); - } - } else { - throw "Use of end without an opening keyword"; - } - } - -} - -class Default extends Command { - constructor(command) { - super(command); - } - - preprocess(commandIndex, commandStackHandler) { - commandStackHandler.setCurrentCommand(this, commandIndex); - commandStackHandler.createAndStoreCommandNode(); - } -} - class CommandStackHandler { constructor(stack) { this._inputStack = stack; @@ -197,12 +25,55 @@ class CommandStackHandler { this.level = 0; } - increaseLevel() { - this.level++; - } - - decreaseLevel() { - this.level--; + preprocessCommand(command, index) { + this._currentCommand = command; + this._currentCommandIndex = index; + switch (command.name) { + case "if": + case "do": + case "times": + this.mutation1(); + break; + case "while": + if (this.topOfState().name === "do") { + this.mutation3(); + } else { + this.mutation1(); + } + break; + case "repeatIf": + if (this.topOfState().name !== "do") { + throw "A repeatIf used without a do block"; + } + this.mutation3(); + break; + case "else": + if (this.topOfState().name !== "if") { + throw "An else / elseIf used outside of an if block"; + } + this.mutation2(); + break; + case "end": + if (this.terminatesLoop()) { + this.mutation4(); + } else if (this.terminatesIf()) { + const elseCount = this.currentSegment().filter(command => command.name === "else").length; + const elseSegment = this.currentSegment().filter(command => command.name.match(/else/)); + if (elseCount > 1) { + throw "Too many else commands used"; + } else if (elseCount === 1 && this.topOf(elseSegment).name !== "else") { + throw "Incorrect command order of elseIf / else"; + } else if (elseCount === 0 || this.topOf(elseSegment).name === "else") { + this.mutation4(); + } + } else { + throw "Use of end without an opening keyword"; + } + break; + default: + this.mutation3(); + break; + } } createAndStoreCommandNode() { @@ -212,18 +83,10 @@ class CommandStackHandler { this.stack.push(node); } - pushState() { - this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); - } - - popState() { - this._state.pop(); - } - topOfState() { let command = this._state[this._state.length - 1]; if (command) { - return this._state[this._state.length - 1]; + return this.topOf(this._state); } else { return { name: "" }; } @@ -247,16 +110,33 @@ class CommandStackHandler { return this._inputStack.slice(this.topOfState().index, this._currentCommandIndex); } - setCurrentCommand(command, index) { - this._currentCommand = command; - this._currentCommandIndex = index; - } - confirmControlFlowSyntax() { if (this._state.length > 0) { throw "Incomplete block at " + this.topOfState().name; } } + + mutation1() { + this.createAndStoreCommandNode(); + this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); + this.level++; + } + + mutation2() { + this.level--; + this.createAndStoreCommandNode(); + this.level++; + } + + mutation3() { + this.createAndStoreCommandNode(); + } + + mutation4() { + this.level--; + this.createAndStoreCommandNode(); + this._state.pop(); + } } class CommandNode { @@ -280,11 +160,10 @@ class PlaybackTree { preprocess() { let commandStackHandler = new CommandStackHandler(this.inputStack); this.inputStack.forEach(function(currentCommand, currentCommandIndex) { - let command = Command.load(currentCommand); - command.preprocess(currentCommandIndex, commandStackHandler); + commandStackHandler.preprocessCommand(currentCommand, currentCommandIndex); }); commandStackHandler.confirmControlFlowSyntax(); - Object.assign(this._preprocessStack, commandStackHandler.stack); + this._preprocessStack = [...commandStackHandler.stack]; return this._preprocessStack; } @@ -313,7 +192,7 @@ class PlaybackTree { } process() { - Object.assign(this.stack, this._preprocessStack); + this.stack = [...this._preprocessStack]; let stack = this.stack; let nextNodeAtSameLevel = this.nextNodeAtSameLevel; let nextEndNode = this.nextEndNode; @@ -322,16 +201,22 @@ class PlaybackTree { let nextCommandNode = stack[currentCommandIndex + 1]; if (nextCommandNode) { if (currentCommandNode.command.isControlFlowCommand() && - !currentCommandNode.command.isEnd()) { + !currentCommandNode.command.isEnd() && + !currentCommandNode.command.isDo()) { currentCommandNode.right = nextCommandNode; currentCommandNode.left = nextNodeAtSameLevel(stack, currentCommandIndex, currentCommandNode.level); } else if (nextCommandNode.command.isControlFlowCommand()) { - let openingNode; - openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); - if (openingNode && !openingNode.command.isLoop()) { - currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); - } else { - currentCommandNode.next = openingNode; + let openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); + if (openingNode) { + if (openingNode.command.isLoop()) { + currentCommandNode.next = openingNode; + } else if (openingNode.command.isDo() && currentCommandNode.command.isWhile()) { + currentCommandNode.next = openingNode; + } else if (!openingNode.command.isLoop() && !openingNode.command.isDo()) { + currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); + } else { + currentCommandNode.next = nextCommandNode; + } } } else { currentCommandNode.next = nextCommandNode; @@ -468,7 +353,7 @@ describe("Control Flow", () => { }); }); describe("Process", () => { - describe("Linked List Validation", () => { + describe.skip("Linked List Validation", () => { test("command-command", () => { let input = [ { name: "command1" }, From 81ca69dbdf3c4a9642288b85fcdebb7067dc8fec Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 07:22:52 -0400 Subject: [PATCH 014/125] Renamed methods and variables in ControlFlowHandler to better denote which is for public vs. internal use. --- .../src/neo/__test__/IO/PlaybackTree.spec.js | 105 +++++++++--------- 1 file changed, 53 insertions(+), 52 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 1694201a3..0a9d5485b 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -19,10 +19,10 @@ class CommandStackHandler { constructor(stack) { this._inputStack = stack; this._state = []; + this._level = 0; this._currentCommand; this._currentCommandIndex; this.stack = []; - this.level = 0; } preprocessCommand(command, index) { @@ -32,111 +32,112 @@ class CommandStackHandler { case "if": case "do": case "times": - this.mutation1(); + this._mutation1(); break; case "while": - if (this.topOfState().name === "do") { - this.mutation3(); + if (this._topOfState().name === "do") { + this._mutation3(); } else { - this.mutation1(); + this._mutation1(); } break; case "repeatIf": - if (this.topOfState().name !== "do") { + if (this._topOfState().name !== "do") { throw "A repeatIf used without a do block"; } - this.mutation3(); + this._mutation3(); break; case "else": - if (this.topOfState().name !== "if") { + if (this._topOfState().name !== "if") { throw "An else / elseIf used outside of an if block"; } - this.mutation2(); + this._mutation2(); break; case "end": - if (this.terminatesLoop()) { - this.mutation4(); - } else if (this.terminatesIf()) { - const elseCount = this.currentSegment().filter(command => command.name === "else").length; - const elseSegment = this.currentSegment().filter(command => command.name.match(/else/)); + if (this._terminatesLoop()) { + this._mutation4(); + } else if (this._terminatesIf()) { + const elseCount = this._currentSegment().filter(command => command.name === "else").length; + const elseSegment = this._currentSegment().filter(command => command.name.match(/else/)); if (elseCount > 1) { throw "Too many else commands used"; - } else if (elseCount === 1 && this.topOf(elseSegment).name !== "else") { + } else if (elseCount === 1 && this._topOf(elseSegment).name !== "else") { throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || this.topOf(elseSegment).name === "else") { - this.mutation4(); + } else if (elseCount === 0 || this._topOf(elseSegment).name === "else") { + this._mutation4(); } } else { throw "Use of end without an opening keyword"; } break; default: - this.mutation3(); + this._mutation3(); break; } } - createAndStoreCommandNode() { + confirmControlFlowSyntax() { + if (this._state.length > 0) { + throw "Incomplete block at " + this._topOfState().name; + } + } + + _createAndStoreCommandNode() { let node = new CommandNode; node.command = this._currentCommand; - node.level = this.level; + node.level = this._level; this.stack.push(node); } - topOfState() { + _topOf(segment) { + return segment[segment.length - 1]; + } + + _topOfState() { let command = this._state[this._state.length - 1]; if (command) { - return this.topOf(this._state); + return this._topOf(this._state); } else { return { name: "" }; } } - topOf(segment) { - return segment[segment.length - 1]; + _currentSegment() { + return this._inputStack.slice(this._topOfState().index, this._currentCommandIndex); } - terminatesIf() { - return (this.topOfState().name === "if"); + _terminatesIf() { + return (this._topOfState().name === "if"); } - terminatesLoop() { - return (this.topOfState().name === "while" || - this.topOfState().name === "times" || - this.topOfState().name === "do"); + _terminatesLoop() { + return (this._topOfState().name === "while" || + this._topOfState().name === "times" || + this._topOfState().name === "do"); } - currentSegment() { - return this._inputStack.slice(this.topOfState().index, this._currentCommandIndex); - } - - confirmControlFlowSyntax() { - if (this._state.length > 0) { - throw "Incomplete block at " + this.topOfState().name; - } - } - - mutation1() { - this.createAndStoreCommandNode(); + _mutation1() { + this._createAndStoreCommandNode(); this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); - this.level++; + this._level++; } - mutation2() { - this.level--; - this.createAndStoreCommandNode(); - this.level++; + _mutation2() { + this._level--; + this._createAndStoreCommandNode(); + this._level++; } - mutation3() { - this.createAndStoreCommandNode(); + _mutation3() { + this._createAndStoreCommandNode(); } - mutation4() { - this.level--; - this.createAndStoreCommandNode(); + _mutation4() { + this._level--; + this._createAndStoreCommandNode(); this._state.pop(); } + } class CommandNode { From 099cf1c6cc42bc6da45920715cceda56d5be6297 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 07:40:37 -0400 Subject: [PATCH 015/125] Broke playback tree classes into individual files --- .../src/neo/IO/playback-tree/CommandNode.js | 27 +++ .../IO/playback-tree/CommandStackHandler.js | 143 ++++++++++++ .../src/neo/IO/playback-tree/PlaybackTree.js | 97 ++++++++ .../src/neo/__test__/IO/PlaybackTree.spec.js | 214 +----------------- 4 files changed, 268 insertions(+), 213 deletions(-) create mode 100644 packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js create mode 100644 packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js create mode 100644 packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js b/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js new file mode 100644 index 000000000..d576bfc46 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js @@ -0,0 +1,27 @@ +// 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 class CommandNode { + constructor() { + this.command; + this.next = undefined; + this.left = undefined; + this.right = undefined; + this.level; + this.timesVisited; + } +} diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js new file mode 100644 index 000000000..00d5b3318 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js @@ -0,0 +1,143 @@ +// 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 { CommandNode } from "./CommandNode"; + +export class CommandStackHandler { + constructor(stack) { + this._inputStack = stack; + this._state = []; + this._level = 0; + this._currentCommand; + this._currentCommandIndex; + this.stack = []; + } + + preprocessCommand(command, index) { + this._currentCommand = command; + this._currentCommandIndex = index; + switch (command.name) { + case "if": + case "do": + case "times": + this._mutation1(); + break; + case "while": + if (this._topOfState().name === "do") { + this._mutation3(); + } else { + this._mutation1(); + } + break; + case "repeatIf": + if (this._topOfState().name !== "do") { + throw "A repeatIf used without a do block"; + } + this._mutation3(); + break; + case "else": + if (this._topOfState().name !== "if") { + throw "An else / elseIf used outside of an if block"; + } + this._mutation2(); + break; + case "end": + if (this._terminatesLoop()) { + this._mutation4(); + } else if (this._terminatesIf()) { + const elseCount = this._currentSegment().filter(command => command.name === "else").length; + const elseSegment = this._currentSegment().filter(command => command.name.match(/else/)); + if (elseCount > 1) { + throw "Too many else commands used"; + } else if (elseCount === 1 && this._topOf(elseSegment).name !== "else") { + throw "Incorrect command order of elseIf / else"; + } else if (elseCount === 0 || this._topOf(elseSegment).name === "else") { + this._mutation4(); + } + } else { + throw "Use of end without an opening keyword"; + } + break; + default: + this._mutation3(); + break; + } + } + + confirmControlFlowSyntax() { + if (this._state.length > 0) { + throw "Incomplete block at " + this._topOfState().name; + } + } + + _createAndStoreCommandNode() { + let node = new CommandNode; + node.command = this._currentCommand; + node.level = this._level; + this.stack.push(node); + } + + _topOf(segment) { + return segment[segment.length - 1]; + } + + _topOfState() { + let command = this._state[this._state.length - 1]; + if (command) { + return this._topOf(this._state); + } else { + return { name: "" }; + } + } + + _currentSegment() { + return this._inputStack.slice(this._topOfState().index, this._currentCommandIndex); + } + + _terminatesIf() { + return (this._topOfState().name === "if"); + } + + _terminatesLoop() { + return (this._topOfState().name === "while" || + this._topOfState().name === "times" || + this._topOfState().name === "do"); + } + + _mutation1() { + this._createAndStoreCommandNode(); + this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); + this._level++; + } + + _mutation2() { + this._level--; + this._createAndStoreCommandNode(); + this._level++; + } + + _mutation3() { + this._createAndStoreCommandNode(); + } + + _mutation4() { + this._level--; + this._createAndStoreCommandNode(); + this._state.pop(); + } + +} diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js b/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js new file mode 100644 index 000000000..2075f50e3 --- /dev/null +++ b/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js @@ -0,0 +1,97 @@ +// 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 { CommandStackHandler } from "./CommandStackHandler"; + +export class PlaybackTree { + constructor(stack) { + this.inputStack = stack; + this._preprocessStack = []; + this.stack = []; + } + + preprocess() { + let commandStackHandler = new CommandStackHandler(this.inputStack); + this.inputStack.forEach(function(currentCommand, currentCommandIndex) { + commandStackHandler.preprocessCommand(currentCommand, currentCommandIndex); + }); + commandStackHandler.confirmControlFlowSyntax(); + this._preprocessStack = [...commandStackHandler.stack]; + return this._preprocessStack; + } + + nextNodeAtSameLevel(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].level === level) { + return stack[i]; + } + } + } + + nextEndNode(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].command.name === "end" && stack[i].level === level) { + return stack[i]; + } + } + } + + previousOpeningNode(stack, index, level) { + for(let i = index; i > -1; i--) { + if (stack[i].level === level - 1) { + return stack[i]; + } + } + } + + process() { + this.stack = [...this._preprocessStack]; + let stack = this.stack; + let nextNodeAtSameLevel = this.nextNodeAtSameLevel; + let nextEndNode = this.nextEndNode; + let previousOpeningNode = this.previousOpeningNode; + stack.forEach(function(currentCommandNode, currentCommandIndex) { + let nextCommandNode = stack[currentCommandIndex + 1]; + if (nextCommandNode) { + if (currentCommandNode.command.isControlFlowCommand() && + !currentCommandNode.command.isEnd() && + !currentCommandNode.command.isDo()) { + currentCommandNode.right = nextCommandNode; + currentCommandNode.left = nextNodeAtSameLevel(stack, currentCommandIndex, currentCommandNode.level); + } else if (nextCommandNode.command.isControlFlowCommand()) { + let openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); + if (openingNode) { + if (openingNode.command.isLoop()) { + currentCommandNode.next = openingNode; + } else if (openingNode.command.isDo() && currentCommandNode.command.isWhile()) { + currentCommandNode.next = openingNode; + } else if (!openingNode.command.isLoop() && !openingNode.command.isDo()) { + currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); + } else { + currentCommandNode.next = nextCommandNode; + } + } + } else { + currentCommandNode.next = nextCommandNode; + } + } + }); + return this.stack; + } + +} + diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js index 0a9d5485b..5586635b4 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js @@ -15,219 +15,7 @@ // specific language governing permissions and limitations // under the License. -class CommandStackHandler { - constructor(stack) { - this._inputStack = stack; - this._state = []; - this._level = 0; - this._currentCommand; - this._currentCommandIndex; - this.stack = []; - } - - preprocessCommand(command, index) { - this._currentCommand = command; - this._currentCommandIndex = index; - switch (command.name) { - case "if": - case "do": - case "times": - this._mutation1(); - break; - case "while": - if (this._topOfState().name === "do") { - this._mutation3(); - } else { - this._mutation1(); - } - break; - case "repeatIf": - if (this._topOfState().name !== "do") { - throw "A repeatIf used without a do block"; - } - this._mutation3(); - break; - case "else": - if (this._topOfState().name !== "if") { - throw "An else / elseIf used outside of an if block"; - } - this._mutation2(); - break; - case "end": - if (this._terminatesLoop()) { - this._mutation4(); - } else if (this._terminatesIf()) { - const elseCount = this._currentSegment().filter(command => command.name === "else").length; - const elseSegment = this._currentSegment().filter(command => command.name.match(/else/)); - if (elseCount > 1) { - throw "Too many else commands used"; - } else if (elseCount === 1 && this._topOf(elseSegment).name !== "else") { - throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || this._topOf(elseSegment).name === "else") { - this._mutation4(); - } - } else { - throw "Use of end without an opening keyword"; - } - break; - default: - this._mutation3(); - break; - } - } - - confirmControlFlowSyntax() { - if (this._state.length > 0) { - throw "Incomplete block at " + this._topOfState().name; - } - } - - _createAndStoreCommandNode() { - let node = new CommandNode; - node.command = this._currentCommand; - node.level = this._level; - this.stack.push(node); - } - - _topOf(segment) { - return segment[segment.length - 1]; - } - - _topOfState() { - let command = this._state[this._state.length - 1]; - if (command) { - return this._topOf(this._state); - } else { - return { name: "" }; - } - } - - _currentSegment() { - return this._inputStack.slice(this._topOfState().index, this._currentCommandIndex); - } - - _terminatesIf() { - return (this._topOfState().name === "if"); - } - - _terminatesLoop() { - return (this._topOfState().name === "while" || - this._topOfState().name === "times" || - this._topOfState().name === "do"); - } - - _mutation1() { - this._createAndStoreCommandNode(); - this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); - this._level++; - } - - _mutation2() { - this._level--; - this._createAndStoreCommandNode(); - this._level++; - } - - _mutation3() { - this._createAndStoreCommandNode(); - } - - _mutation4() { - this._level--; - this._createAndStoreCommandNode(); - this._state.pop(); - } - -} - -class CommandNode { - constructor() { - this.command; - this.next = undefined; - this.left = undefined; - this.right = undefined; - this.level; - this.timesVisited; - } -} - -class PlaybackTree { - constructor(stack) { - this.inputStack = stack; - this._preprocessStack = []; - this.stack = []; - } - - preprocess() { - let commandStackHandler = new CommandStackHandler(this.inputStack); - this.inputStack.forEach(function(currentCommand, currentCommandIndex) { - commandStackHandler.preprocessCommand(currentCommand, currentCommandIndex); - }); - commandStackHandler.confirmControlFlowSyntax(); - this._preprocessStack = [...commandStackHandler.stack]; - return this._preprocessStack; - } - - nextNodeAtSameLevel(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].level === level) { - return stack[i]; - } - } - } - - nextEndNode(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].command.name === "end" && stack[i].level === level) { - return stack[i]; - } - } - } - - previousOpeningNode(stack, index, level) { - for(let i = index; i > -1; i--) { - if (stack[i].level === level - 1) { - return stack[i]; - } - } - } - - process() { - this.stack = [...this._preprocessStack]; - let stack = this.stack; - let nextNodeAtSameLevel = this.nextNodeAtSameLevel; - let nextEndNode = this.nextEndNode; - let previousOpeningNode = this.previousOpeningNode; - stack.forEach(function(currentCommandNode, currentCommandIndex) { - let nextCommandNode = stack[currentCommandIndex + 1]; - if (nextCommandNode) { - if (currentCommandNode.command.isControlFlowCommand() && - !currentCommandNode.command.isEnd() && - !currentCommandNode.command.isDo()) { - currentCommandNode.right = nextCommandNode; - currentCommandNode.left = nextNodeAtSameLevel(stack, currentCommandIndex, currentCommandNode.level); - } else if (nextCommandNode.command.isControlFlowCommand()) { - let openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); - if (openingNode) { - if (openingNode.command.isLoop()) { - currentCommandNode.next = openingNode; - } else if (openingNode.command.isDo() && currentCommandNode.command.isWhile()) { - currentCommandNode.next = openingNode; - } else if (!openingNode.command.isLoop() && !openingNode.command.isDo()) { - currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); - } else { - currentCommandNode.next = nextCommandNode; - } - } - } else { - currentCommandNode.next = nextCommandNode; - } - } - }); - return this.stack; - } - -} +import { PlaybackTree } from "../../IO/playback-tree/PlaybackTree"; describe("Control Flow", () => { describe("Preprocess", () => { From 2268a4b2fdb384a3f4d181b88fd15436b1b76f3a Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 07:42:49 -0400 Subject: [PATCH 016/125] Deleted previous attempt at control flow validation --- .../src/neo/IO/ControlFlowValidator.js | 91 ------------ .../__test__/IO/ControlFlowValidator.spec.js | 134 ------------------ 2 files changed, 225 deletions(-) delete mode 100644 packages/selenium-ide/src/neo/IO/ControlFlowValidator.js delete mode 100644 packages/selenium-ide/src/neo/__test__/IO/ControlFlowValidator.spec.js diff --git a/packages/selenium-ide/src/neo/IO/ControlFlowValidator.js b/packages/selenium-ide/src/neo/IO/ControlFlowValidator.js deleted file mode 100644 index 0f8a1f512..000000000 --- a/packages/selenium-ide/src/neo/IO/ControlFlowValidator.js +++ /dev/null @@ -1,91 +0,0 @@ -// 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 ControlFlowValidator { - constructor(commandStack) { - this.commandStack = commandStack; - this.commandKeywordPairs = [ - ["if", "end"], - ["while", "endWhile"] - ]; - } - - process() { - if (this.isBoundByACompleteSegment()) { - if (this.hasIncompleteCommandSegments()) { - return false; - } else { - return true; - } - } else if (this.hasMoreThanOneCompleteSegment()) { - if (this.segmentsContainIncompleteSegments()) { - return false; - } else { - return true; - } - } else { - return false; - } - } - - segmentsContainIncompleteSegments() { - //let keywordIndices = []; - //let stack = this.commandStack; - //this.commandKeywordPairs.forEach(function(keywordPair) { - // keywordIndices.push([ - // stack.indexOf(keywordPair[0]), - // stack.indexOf(keywordPair[1]) - // ]); - //}); - //console.log(keywordIndices); - const ifIndex = this.commandStack.indexOf("if"); - const endIndex = this.commandStack.indexOf("end"); - const segment1 = this.commandStack.slice(ifIndex, endIndex + 1); - const whileIndex = this.commandStack.indexOf("while"); - const endWhileIndex = this.commandStack.indexOf("endWhile"); - const segment2 = this.commandStack.slice(whileIndex, endWhileIndex + 1); - return (this.hasIncompleteCommandSegments(segment1) || this.hasIncompleteCommandSegments(segment2)); - } - - startsWith(command) { - return this.commandStack[0] === command; - } - - endsWith(command) { - return this.commandStack[this.commandStack.length - 1] === command; - } - - commandCount(commandName, segment) { - const stack = (segment ? segment : this.commandStack); - return stack.filter(command => command === commandName).length; - } - - isBoundByACompleteSegment() { - return (this.startsWith("if") && this.endsWith("end") || - this.startsWith("while") && this.endsWith("endWhile")); - } - - hasIncompleteCommandSegments(segment) { - return (this.commandCount("if", segment) !== this.commandCount("end", segment) || - this.commandCount("while", segment) !== this.commandCount("endWhile", segment)); - } - - hasMoreThanOneCompleteSegment() { - return ((this.commandCount("if") > 0 && this.commandCount("if") === this.commandCount("end")) && - (this.commandCount("while") > 0 && this.commandCount("while") === this.commandCount("endWhile"))); - } -} diff --git a/packages/selenium-ide/src/neo/__test__/IO/ControlFlowValidator.spec.js b/packages/selenium-ide/src/neo/__test__/IO/ControlFlowValidator.spec.js deleted file mode 100644 index ac6aafb69..000000000 --- a/packages/selenium-ide/src/neo/__test__/IO/ControlFlowValidator.spec.js +++ /dev/null @@ -1,134 +0,0 @@ -// 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 ControlFlowValidator from "../../IO/ControlFlowValidator"; - -describe("Control Flow Validations", () => { - test("if-end block", () => { - let validator = new ControlFlowValidator(["if", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-if-end-end block", () => { - let validator = new ControlFlowValidator(["if", "if", "end", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-else-end block", () => { - let validator = new ControlFlowValidator(["if", "else", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-elseif-end block", () => { - let validator = new ControlFlowValidator(["if", "elseIf", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-elseif-elseif-elseif-end block", () => { - let validator = new ControlFlowValidator(["if", "elseIf", "elseIf", "elseIf", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-while-endwhile-end block", () => { - let validator = new ControlFlowValidator(["if", "while", "endWhile", "end"]); - expect(validator.process()).toBeTruthy(); - }); - - test("while-endwhile block", () => { - let validator = new ControlFlowValidator(["while", "endWhile"]); - expect(validator.process()).toBeTruthy(); - }); - - test("while-if-end-endwhile block", () => { - let validator = new ControlFlowValidator(["while", "if", "end", "endWhile"]); - expect(validator.process()).toBeTruthy(); - }); - - test("if-end-while-endwhile block", () => { - let validator = new ControlFlowValidator(["if", "end", "while", "endWhile"]); - expect(validator.process()).toBeTruthy(); - }); -}); - -describe("Control Flow Invalidations", () => { - test.skip("returns command that invalidates segment", () => { - }); - - test.skip("returns commands that invalidate segment", () => { - }); - - test("if block", () => { - let validator = new ControlFlowValidator(["if"]); - expect(validator.process()).toBeFalsy(); - }); - - test("end-if block", () => { - let validator = new ControlFlowValidator(["end", "if"]); - expect(validator.process()).toBeFalsy(); - }); - - test("if-if-end block", () => { - let validator = new ControlFlowValidator(["if", "if", "end"]); - expect(validator.process()).toBeFalsy(); - }); - - test("else-if-end block", () => { - let validator = new ControlFlowValidator(["else", "if", "end"]); - expect(validator.process()).toBeFalsy(); - }); - - test("if-while-end-endwhile block", () => { - let validator = new ControlFlowValidator(["if", "while", "end", "endWhile"]); - expect(validator.process()).toBeFalsy(); - }); - - test("while-if-endwhile-end block", () => { - let validator = new ControlFlowValidator(["while", "if", "endWhile", "end"]); - expect(validator.process()).toBeFalsy(); - }); - - test("while-if-endwhile block", () => { - let validator = new ControlFlowValidator(["while", "if", "endWhile"]); - expect(validator.process()).toBeFalsy(); - }); - - test("while block", () => { - let validator = new ControlFlowValidator(["while"]); - expect(validator.process()).toBeFalsy(); - }); - - test("if-while-end block", () => { - let validator = new ControlFlowValidator(["if", "while", "end"]); - expect(validator.process()).toBeFalsy(); - }); - - test("if-end-while-if-endwhile block", () => { - let validator = new ControlFlowValidator(["if", "end", "while", "if", "endWhile"]); - expect(validator.process()).toBeFalsy(); - }); - - test.skip("if-else-elseif-end block", () => { - let validator = new ControlFlowValidator(["if", "else", "elseIf", "end"]); - expect(validator.process()).toBeFalsy(); - }); - - test.skip("while-else-endwhile block", () => { - let validator = new ControlFlowValidator(["while", "else", "endWhile"]); - expect(validator.process()).toBeFalsy(); - }); - -}); From b1a621a7215a886e0aee320d884f0ea899e183f1 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 07:57:56 -0400 Subject: [PATCH 017/125] Reordered commands within #mutation1 --- .../src/neo/IO/playback-tree/CommandStackHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js index 00d5b3318..5155c2624 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js @@ -119,8 +119,8 @@ export class CommandStackHandler { } _mutation1() { - this._createAndStoreCommandNode(); this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); + this._createAndStoreCommandNode(); this._level++; } From 56315bf477829fdc24877677e1e4f586c1447f35 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 07:59:21 -0400 Subject: [PATCH 018/125] Moved PlaybacTree.spec.js into a playback-tree directory to match the file/directory structure outside of __test__ --- .../src/neo/__test__/IO/{ => playback-tree}/PlaybackTree.spec.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/selenium-ide/src/neo/__test__/IO/{ => playback-tree}/PlaybackTree.spec.js (100%) diff --git a/packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js similarity index 100% rename from packages/selenium-ide/src/neo/__test__/IO/PlaybackTree.spec.js rename to packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js From 13ac6c1abcd0c985320e7d6ae7d673bfd8d75612 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 08:03:40 -0400 Subject: [PATCH 019/125] Fixed import statement --- .../src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js index 5586635b4..d76e43c3b 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { PlaybackTree } from "../../IO/playback-tree/PlaybackTree"; +import { PlaybackTree } from "../../../IO/playback-tree/PlaybackTree"; describe("Control Flow", () => { describe("Preprocess", () => { From d820ffd53388f88e032ec1d015be60bfbce2e6b4 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 13:27:14 -0400 Subject: [PATCH 020/125] Made it to so CommandStackHandler can loop through and preprocess the commands itself. Also refactored checks against the command name into functions, and renamed the mutation methods into something more descriptive. --- .../IO/playback-tree/CommandStackHandler.js | 82 +++++++++++-------- .../src/neo/IO/playback-tree/PlaybackTree.js | 5 +- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js index 5155c2624..c48b76e5f 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js @@ -17,6 +17,24 @@ import { CommandNode } from "./CommandNode"; +function isIf(command) { + return (command.name === "if"); +} + +function isElse(command) { + return (command.name === "else"); +} + +function isDo(command) { + return (command.name === "do"); +} + +function isLoop(command) { + return (command.name === "while" || + command.name === "times" || + command.name === "do"); +} + export class CommandStackHandler { constructor(stack) { this._inputStack = stack; @@ -27,53 +45,61 @@ export class CommandStackHandler { this.stack = []; } - preprocessCommand(command, index) { - this._currentCommand = command; - this._currentCommandIndex = index; - switch (command.name) { + preprocessCommands() { + let that = this; + this._inputStack.forEach(function(currentCommand, currentCommandIndex) { + that._currentCommand = currentCommand; + that._currentCommandIndex = currentCommandIndex; + that._preprocessCommand(); + }); + this.confirmControlFlowSyntax(); + } + + _preprocessCommand() { + switch (this._currentCommand.name) { case "if": case "do": case "times": - this._mutation1(); + this._trackControlFlowBranchOpening(); break; case "while": - if (this._topOfState().name === "do") { - this._mutation3(); + if (isDo(this._topOfState())) { + this._trackCommand(); } else { - this._mutation1(); + this._trackControlFlowBranchOpening(); } break; case "repeatIf": - if (this._topOfState().name !== "do") { + if (!isDo(this._topOfState())) { throw "A repeatIf used without a do block"; } - this._mutation3(); + this._trackCommand(); break; case "else": - if (this._topOfState().name !== "if") { + if (!isIf(this._topOfState())) { throw "An else / elseIf used outside of an if block"; } - this._mutation2(); + this._trackControlFlowCommandElse(); break; case "end": - if (this._terminatesLoop()) { - this._mutation4(); - } else if (this._terminatesIf()) { + if (isLoop(this._topOfState())) { + this._trackControlFlowBranchEnding(); + } else if (isIf(this._topOfState())) { const elseCount = this._currentSegment().filter(command => command.name === "else").length; const elseSegment = this._currentSegment().filter(command => command.name.match(/else/)); if (elseCount > 1) { throw "Too many else commands used"; - } else if (elseCount === 1 && this._topOf(elseSegment).name !== "else") { + } else if (elseCount === 1 && !isElse(this._topOf(elseSegment))) { throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || this._topOf(elseSegment).name === "else") { - this._mutation4(); + } else if (elseCount === 0 || isElse(this._topOf(elseSegment))) { + this._trackControlFlowBranchEnding(); } } else { throw "Use of end without an opening keyword"; } break; default: - this._mutation3(); + this._trackCommand(); break; } } @@ -108,33 +134,23 @@ export class CommandStackHandler { return this._inputStack.slice(this._topOfState().index, this._currentCommandIndex); } - _terminatesIf() { - return (this._topOfState().name === "if"); - } - - _terminatesLoop() { - return (this._topOfState().name === "while" || - this._topOfState().name === "times" || - this._topOfState().name === "do"); - } - - _mutation1() { + _trackControlFlowBranchOpening() { this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); this._createAndStoreCommandNode(); this._level++; } - _mutation2() { + _trackControlFlowCommandElse() { this._level--; this._createAndStoreCommandNode(); this._level++; } - _mutation3() { + _trackCommand() { this._createAndStoreCommandNode(); } - _mutation4() { + _trackControlFlowBranchEnding() { this._level--; this._createAndStoreCommandNode(); this._state.pop(); diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js b/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js index 2075f50e3..309447870 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js @@ -26,10 +26,7 @@ export class PlaybackTree { preprocess() { let commandStackHandler = new CommandStackHandler(this.inputStack); - this.inputStack.forEach(function(currentCommand, currentCommandIndex) { - commandStackHandler.preprocessCommand(currentCommand, currentCommandIndex); - }); - commandStackHandler.confirmControlFlowSyntax(); + commandStackHandler.preprocessCommands(); this._preprocessStack = [...commandStackHandler.stack]; return this._preprocessStack; } From 1e0042ae6310ec83e97f7be809235dc7c5df9214 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 14:35:57 -0400 Subject: [PATCH 021/125] Reduced CommandStackHandler and PlaybackTree into just PlaybackTree. Moved it to the root of the IO directory, updated tests accordingly, and removed unnecessary files and folders. Also added additional isCommandType style functions and refactored all command type checks to use them. --- ...ommandStackHandler.js => playback-tree.js} | 130 ++++++++++++++---- .../src/neo/IO/playback-tree/CommandNode.js | 27 ---- .../src/neo/IO/playback-tree/PlaybackTree.js | 94 ------------- .../neo/__test__/IO/CommandHandler.spec.js | 25 ---- ...backTree.spec.js => playback-tree.spec.js} | 72 +++++----- 5 files changed, 144 insertions(+), 204 deletions(-) rename packages/selenium-ide/src/neo/IO/{playback-tree/CommandStackHandler.js => playback-tree.js} (51%) delete mode 100644 packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js delete mode 100644 packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js delete mode 100644 packages/selenium-ide/src/neo/__test__/IO/CommandHandler.spec.js rename packages/selenium-ide/src/neo/__test__/IO/{playback-tree/PlaybackTree.spec.js => playback-tree.spec.js} (80%) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js b/packages/selenium-ide/src/neo/IO/playback-tree.js similarity index 51% rename from packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js rename to packages/selenium-ide/src/neo/IO/playback-tree.js index c48b76e5f..07a09e343 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree/CommandStackHandler.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree.js @@ -15,18 +15,42 @@ // specific language governing permissions and limitations // under the License. -import { CommandNode } from "./CommandNode"; +class CommandNode { + constructor() { + this.command; + this.next = undefined; + this.left = undefined; + this.right = undefined; + this.level; + this.timesVisited; + } +} -function isIf(command) { - return (command.name === "if"); +function isControlFlowCommand(command) { + return (command.name === "do" || + command.name === "else" || + command.name === "elseIf" || + command.name === "end" || + command.name === "if" || + command.name === "repeatIf" || + command.name === "times" || + command.name === "while"); +} + +function isDo(command) { + return (command.name === "do"); } function isElse(command) { return (command.name === "else"); } -function isDo(command) { - return (command.name === "do"); +function isEnd(command) { + return (command.name === "end"); +} + +function isIf(command) { + return (command.name === "if"); } function isLoop(command) { @@ -35,24 +59,31 @@ function isLoop(command) { command.name === "do"); } -export class CommandStackHandler { - constructor(stack) { - this._inputStack = stack; +function isWhile(command) { + return (command.name === "while"); +} + +export class PlaybackTree { + constructor(commandStack) { + this._commandStack = commandStack; this._state = []; this._level = 0; this._currentCommand; this._currentCommandIndex; - this.stack = []; + this._commandNodeStack = []; } - preprocessCommands() { + _preprocessCommands() { let that = this; - this._inputStack.forEach(function(currentCommand, currentCommandIndex) { + this._commandStack.forEach(function(currentCommand, currentCommandIndex) { that._currentCommand = currentCommand; that._currentCommandIndex = currentCommandIndex; that._preprocessCommand(); }); - this.confirmControlFlowSyntax(); + if (this._state.length > 0) { + throw "Incomplete block at " + this._topOfState().name; + } + return true; } _preprocessCommand() { @@ -85,13 +116,13 @@ export class CommandStackHandler { if (isLoop(this._topOfState())) { this._trackControlFlowBranchEnding(); } else if (isIf(this._topOfState())) { - const elseCount = this._currentSegment().filter(command => command.name === "else").length; - const elseSegment = this._currentSegment().filter(command => command.name.match(/else/)); - if (elseCount > 1) { + const numberOfElse = this._currentSegment().filter(command => isElse(command)).length; + const allElseInCurrentSegment = this._currentSegment().filter(command => command.name.match(/else/)); + if (numberOfElse > 1) { throw "Too many else commands used"; - } else if (elseCount === 1 && !isElse(this._topOf(elseSegment))) { + } else if (numberOfElse === 1 && !isElse(this._topOf(allElseInCurrentSegment))) { throw "Incorrect command order of elseIf / else"; - } else if (elseCount === 0 || isElse(this._topOf(elseSegment))) { + } else if (numberOfElse === 0 || isElse(this._topOf(allElseInCurrentSegment))) { this._trackControlFlowBranchEnding(); } } else { @@ -104,17 +135,11 @@ export class CommandStackHandler { } } - confirmControlFlowSyntax() { - if (this._state.length > 0) { - throw "Incomplete block at " + this._topOfState().name; - } - } - _createAndStoreCommandNode() { let node = new CommandNode; node.command = this._currentCommand; node.level = this._level; - this.stack.push(node); + this._commandNodeStack.push(node); } _topOf(segment) { @@ -131,7 +156,7 @@ export class CommandStackHandler { } _currentSegment() { - return this._inputStack.slice(this._topOfState().index, this._currentCommandIndex); + return this._commandStack.slice(this._topOfState().index, this._currentCommandIndex); } _trackControlFlowBranchOpening() { @@ -156,4 +181,59 @@ export class CommandStackHandler { this._state.pop(); } + _nextNodeAtSameLevel(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].level === level) { + return stack[i]; + } + } + } + + _nextEndNode(stack, index, level) { + for(let i = index + 1; i !== stack.length; i++) { + if (stack[i].command.name === "end" && stack[i].level === level) { + return stack[i]; + } + } + } + + _previousOpeningNode(stack, index, level) { + for(let i = index; i > -1; i--) { + if (stack[i].level === level - 1) { + return stack[i]; + } + } + } + + _process() { + let that = this; + that._commandNodeStack.forEach(function(currentCommandNode, currentCommandIndex) { + let nextCommandNode = that._commandNodeStack[currentCommandIndex + 1]; + if (nextCommandNode) { + if (isControlFlowCommand(currentCommandNode.command) && + !isEnd(currentCommandNode.command) && + !isDo(currentCommandNode.command)) { + currentCommandNode.right = nextCommandNode; + currentCommandNode.left = that._nextNodeAtSameLevel(that._commandNodeStack, currentCommandIndex, currentCommandNode.level); + } else if (isControlFlowCommand(nextCommandNode.command)) { + let openingNode = that._previousOpeningNode(that._commandNodeStack, currentCommandIndex, currentCommandNode.level); + if (openingNode) { + if (isLoop(openingNode.command)) { + currentCommandNode.next = openingNode; + } else if (isDo(openingNode.command) && isWhile(currentCommandNode.command)) { + currentCommandNode.next = openingNode; + } else if (!isLoop(openingNode.command) && !isDo(openingNode.command)) { + currentCommandNode.next = that._nextEndNode(that._commandNodeStack, currentCommandIndex, openingNode.level); + } else { + currentCommandNode.next = nextCommandNode; + } + } + } else { + currentCommandNode.next = nextCommandNode; + } + } + }); + return this.stack; + } + } diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js b/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js deleted file mode 100644 index d576bfc46..000000000 --- a/packages/selenium-ide/src/neo/IO/playback-tree/CommandNode.js +++ /dev/null @@ -1,27 +0,0 @@ -// 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 class CommandNode { - constructor() { - this.command; - this.next = undefined; - this.left = undefined; - this.right = undefined; - this.level; - this.timesVisited; - } -} diff --git a/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js b/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js deleted file mode 100644 index 309447870..000000000 --- a/packages/selenium-ide/src/neo/IO/playback-tree/PlaybackTree.js +++ /dev/null @@ -1,94 +0,0 @@ -// 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 { CommandStackHandler } from "./CommandStackHandler"; - -export class PlaybackTree { - constructor(stack) { - this.inputStack = stack; - this._preprocessStack = []; - this.stack = []; - } - - preprocess() { - let commandStackHandler = new CommandStackHandler(this.inputStack); - commandStackHandler.preprocessCommands(); - this._preprocessStack = [...commandStackHandler.stack]; - return this._preprocessStack; - } - - nextNodeAtSameLevel(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].level === level) { - return stack[i]; - } - } - } - - nextEndNode(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].command.name === "end" && stack[i].level === level) { - return stack[i]; - } - } - } - - previousOpeningNode(stack, index, level) { - for(let i = index; i > -1; i--) { - if (stack[i].level === level - 1) { - return stack[i]; - } - } - } - - process() { - this.stack = [...this._preprocessStack]; - let stack = this.stack; - let nextNodeAtSameLevel = this.nextNodeAtSameLevel; - let nextEndNode = this.nextEndNode; - let previousOpeningNode = this.previousOpeningNode; - stack.forEach(function(currentCommandNode, currentCommandIndex) { - let nextCommandNode = stack[currentCommandIndex + 1]; - if (nextCommandNode) { - if (currentCommandNode.command.isControlFlowCommand() && - !currentCommandNode.command.isEnd() && - !currentCommandNode.command.isDo()) { - currentCommandNode.right = nextCommandNode; - currentCommandNode.left = nextNodeAtSameLevel(stack, currentCommandIndex, currentCommandNode.level); - } else if (nextCommandNode.command.isControlFlowCommand()) { - let openingNode = previousOpeningNode(stack, currentCommandIndex, currentCommandNode.level); - if (openingNode) { - if (openingNode.command.isLoop()) { - currentCommandNode.next = openingNode; - } else if (openingNode.command.isDo() && currentCommandNode.command.isWhile()) { - currentCommandNode.next = openingNode; - } else if (!openingNode.command.isLoop() && !openingNode.command.isDo()) { - currentCommandNode.next = nextEndNode(stack, currentCommandIndex, openingNode.level); - } else { - currentCommandNode.next = nextCommandNode; - } - } - } else { - currentCommandNode.next = nextCommandNode; - } - } - }); - return this.stack; - } - -} - diff --git a/packages/selenium-ide/src/neo/__test__/IO/CommandHandler.spec.js b/packages/selenium-ide/src/neo/__test__/IO/CommandHandler.spec.js deleted file mode 100644 index 141ace0e8..000000000 --- a/packages/selenium-ide/src/neo/__test__/IO/CommandHandler.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -// 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 { registerCommand, canExecuteCommand, executeCommand } from "../../plugin/commandExecutor"; -//import { CommandHandler } from "../IO/commandHandler"; - -describe("Command Handler", () => { - it.skip("should check for command type (e.g., extCommand, Selenium, Control Flow", () => { }); - it.skip("should derive the correct command object from the provided type", () => { }); - it.skip("should validate the test case (through delegation)", () => { }); -}); diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js similarity index 80% rename from packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js rename to packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js index d76e43c3b..e1a092878 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree/PlaybackTree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { PlaybackTree } from "../../../IO/playback-tree/PlaybackTree"; +import { PlaybackTree } from "../../IO/playback-tree"; describe("Control Flow", () => { describe("Preprocess", () => { @@ -38,7 +38,8 @@ describe("Control Flow", () => { { name: "end" }, { name: "end" } ]); - let stack = playbackTree.preprocess(); + playbackTree._preprocessCommands(); + let stack = playbackTree._commandNodeStack; expect(stack[0].level).toEqual(0); // if expect(stack[1].level).toEqual(1); // command expect(stack[2].level).toEqual(0); // else @@ -59,98 +60,99 @@ describe("Control Flow", () => { describe("Syntax Validation", () => { test("if, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, else, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, elseIf, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, elseIf, else, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "else" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("while, end", () => { let playbackTree = new PlaybackTree([{ name: "while" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("times, end", () => { let playbackTree = new PlaybackTree([{ name: "times" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("do, repeatIf, end", () => { let playbackTree = new PlaybackTree([{ name: "do" }, { name: "repeatIf" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("do, while, end", () => { let playbackTree = new PlaybackTree([{ name: "do" }, { name: "while" }, { name: "end" }]); - expect(playbackTree.preprocess()).toBeTruthy(); + expect(playbackTree._preprocessCommands()).toBeTruthy(); }); }); describe("Syntax Invalidation", () => { test("if", () => { let playbackTree = new PlaybackTree([{ name: "if" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "if" }, { name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incorrect command order of elseIf / else"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incorrect command order of elseIf / else"); }); test("if, else, else, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "else" }, { name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Too many else commands used"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Too many else commands used"); }); test("while", () => { let playbackTree = new PlaybackTree([{ name: "while" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at while"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); }); test("if, while", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at while"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at if"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "else" }, { name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("An else / elseIf used outside of an if block"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("An else / elseIf used outside of an if block"); }); test("times", () => { let playbackTree = new PlaybackTree([{ name: "times" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at times"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at times"); }); test("repeatIf", () => { let playbackTree = new PlaybackTree([{ name: "repeatIf" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("A repeatIf used without a do block"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("A repeatIf used without a do block"); }); test("do", () => { let playbackTree = new PlaybackTree([{ name: "do" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Incomplete block at do"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at do"); }); test("end", () => { let playbackTree = new PlaybackTree([{ name: "end" }]); - expect(function() { playbackTree.preprocess(); }).toThrow("Use of end without an opening keyword"); + expect(function() { playbackTree._preprocessCommands(); }).toThrow("Use of end without an opening keyword"); }); }); }); describe("Process", () => { - describe.skip("Linked List Validation", () => { + describe("Linked List Validation", () => { test("command-command", () => { let input = [ { name: "command1" }, { name: "command2" } ]; let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - let stack = playbackTree.process(); + playbackTree._preprocessCommands(); + playbackTree._process(); + let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[1].command).toEqual(input[1]); expect(stack[0].next).toEqual(stack[1]); @@ -169,8 +171,9 @@ describe("Control Flow", () => { { name: "end" } ]; let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - let stack = playbackTree.process(); + playbackTree._preprocessCommands(); + playbackTree._process(); + let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -199,8 +202,9 @@ describe("Control Flow", () => { { name: "end" } ]; let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - let stack = playbackTree.process(); + playbackTree._preprocessCommands(); + playbackTree._process(); + let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -225,8 +229,9 @@ describe("Control Flow", () => { { name: "end" } ]; let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - let stack = playbackTree.process(); + playbackTree._preprocessCommands(); + playbackTree._process(); + let stack = playbackTree._commandNodeStack; // if expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); @@ -271,8 +276,9 @@ describe("Control Flow", () => { { name: "end" } ]; let playbackTree = new PlaybackTree(input); - playbackTree.preprocess(); - let stack = playbackTree.process(); + playbackTree._preprocessCommands(); + playbackTree._process(); + let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); From fa6810eba412db728de645bfe9efddbca5bbc382 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 22:58:31 -0400 Subject: [PATCH 022/125] Moved command node processing over to a switch statement that leverages a push down automata and finished implementing the currently written test cases. --- .../selenium-ide/src/neo/IO/playback-tree.js | 109 +++++++++++------- .../src/neo/__test__/IO/playback-tree.spec.js | 63 ++++++---- 2 files changed, 111 insertions(+), 61 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree.js b/packages/selenium-ide/src/neo/IO/playback-tree.js index 07a09e343..69a237be1 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree.js @@ -80,7 +80,7 @@ export class PlaybackTree { that._currentCommandIndex = currentCommandIndex; that._preprocessCommand(); }); - if (this._state.length > 0) { + if (!this._isStateEmpty()) { throw "Incomplete block at " + this._topOfState().name; } return true; @@ -107,6 +107,7 @@ export class PlaybackTree { this._trackCommand(); break; case "else": + case "elseIf": if (!isIf(this._topOfState())) { throw "An else / elseIf used outside of an if block"; } @@ -155,6 +156,10 @@ export class PlaybackTree { } } + _isStateEmpty() { + return (this._state.length === 0); + } + _currentSegment() { return this._commandStack.slice(this._topOfState().index, this._currentCommandIndex); } @@ -181,59 +186,81 @@ export class PlaybackTree { this._state.pop(); } - _nextNodeAtSameLevel(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].level === level) { - return stack[i]; + _findNextNodeAtLevel(index, level) { + for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { + if (this._commandNodeStack[i].level === level) { + return this._commandNodeStack[i]; } } } - _nextEndNode(stack, index, level) { - for(let i = index + 1; i !== stack.length; i++) { - if (stack[i].command.name === "end" && stack[i].level === level) { - return stack[i]; + _findNextEndNodeAtLevel(index, level) { + for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { + if (this._commandNodeStack[i].level === level && + this._commandNodeStack[i].command.name === "end") { + return this._commandNodeStack[i]; } } } - _previousOpeningNode(stack, index, level) { - for(let i = index; i > -1; i--) { - if (stack[i].level === level - 1) { - return stack[i]; - } - } + _processCommands() { + this._state = []; + let that = this; + that._commandNodeStack.forEach(function(currentCommandNode, currentCommandNodeIndex) { + that._processCommandNode(currentCommandNode, currentCommandNodeIndex); + }); } - _process() { - let that = this; - that._commandNodeStack.forEach(function(currentCommandNode, currentCommandIndex) { - let nextCommandNode = that._commandNodeStack[currentCommandIndex + 1]; - if (nextCommandNode) { - if (isControlFlowCommand(currentCommandNode.command) && - !isEnd(currentCommandNode.command) && - !isDo(currentCommandNode.command)) { - currentCommandNode.right = nextCommandNode; - currentCommandNode.left = that._nextNodeAtSameLevel(that._commandNodeStack, currentCommandIndex, currentCommandNode.level); - } else if (isControlFlowCommand(nextCommandNode.command)) { - let openingNode = that._previousOpeningNode(that._commandNodeStack, currentCommandIndex, currentCommandNode.level); - if (openingNode) { - if (isLoop(openingNode.command)) { - currentCommandNode.next = openingNode; - } else if (isDo(openingNode.command) && isWhile(currentCommandNode.command)) { - currentCommandNode.next = openingNode; - } else if (!isLoop(openingNode.command) && !isDo(openingNode.command)) { - currentCommandNode.next = that._nextEndNode(that._commandNodeStack, currentCommandIndex, openingNode.level); + _processCommandNode(commandNode, commandNodeIndex) { + let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; + if (nextCommandNode) { + switch(commandNode.command.name) { + case "do": + this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + commandNode.next = nextCommandNode; + break; + case "if": + this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + commandNode.right = nextCommandNode; + commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); + break; + case "else": + commandNode.next = nextCommandNode; + break; + case "elseIf": + commandNode.right = nextCommandNode; + commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); + break; + case "while": + if (isDo(this._topOfState())) { + commandNode.right = this._commandNodeStack[this._topOfState().index]; + commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); + } else { + this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + commandNode.right = nextCommandNode; + commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, commandNode.level); + } + break; + case "end": + this._state.pop(); + if (!this._isStateEmpty()) { + if (isControlFlowCommand(nextCommandNode.command)) { + commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); } else { - currentCommandNode.next = nextCommandNode; + commandNode.next = nextCommandNode; } } - } else { - currentCommandNode.next = nextCommandNode; - } + break; + default: + if (isIf(this._topOfState()) && isControlFlowCommand(nextCommandNode.command)) { + commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); + } else if (isWhile(this._topOfState()) && isControlFlowCommand(nextCommandNode.command)) { + commandNode.next = this._commandNodeStack[this._topOfState().index]; + } else { + commandNode.next = nextCommandNode; + } + break; } - }); - return this.stack; + } } - } diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js index e1a092878..591f0d989 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js @@ -151,7 +151,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._process(); + playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[1].command).toEqual(input[1]); @@ -162,38 +162,55 @@ describe("Control Flow", () => { expect(stack[1].left).toBeUndefined(); expect(stack[1].right).toBeUndefined(); }); - test("if-command-else-command-end", () => { + test("if-command-elseIf-command-else-command-end", () => { let input = [ { name: "if" }, { name: "command" }, + { name: "elseIf" }, + { name: "command" }, { name: "else" }, { name: "command" }, { name: "end" } ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._process(); + playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; + // if expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); + // command expect(stack[1].command).toEqual(input[1]); - expect(stack[1].next).toEqual(stack[4]); + expect(stack[1].next).toEqual(stack[6]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); + // elseIf expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toEqual(stack[3]); expect(stack[2].left).toEqual(stack[4]); + // command expect(stack[3].command).toEqual(input[3]); - expect(stack[3].next).toEqual(stack[4]); + expect(stack[3].next).toEqual(stack[6]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); + // else expect(stack[4].command).toEqual(input[4]); - expect(stack[4].next).toBeUndefined(); + expect(stack[4].next).toEqual(stack[5]); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); + // command + expect(stack[5].command).toEqual(input[5]); + expect(stack[5].next).toEqual(stack[6]); + expect(stack[5].right).toBeUndefined(); + expect(stack[5].left).toBeUndefined(); + // end + expect(stack[6].command).toEqual(input[6]); + expect(stack[6].next).toBeUndefined(); + expect(stack[6].right).toBeUndefined(); + expect(stack[6].left).toBeUndefined(); }); test("while-command-end", () => { let input = [ @@ -203,7 +220,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._process(); + playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); @@ -224,19 +241,20 @@ describe("Control Flow", () => { { name: "while" }, { name: "command" }, { name: "end" }, + { name: "command" }, { name: "else" }, { name: "command" }, { name: "end" } ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._process(); + playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; // if expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[4]); + expect(stack[0].left).toEqual(stack[5]); // while expect(stack[1].command).toEqual(input[1]); expect(stack[1].next).toBeUndefined(); @@ -249,26 +267,31 @@ describe("Control Flow", () => { expect(stack[2].left).toBeUndefined(); // end expect(stack[3].command).toEqual(input[3]); - expect(stack[3].next).toEqual(stack[6]); + expect(stack[3].next).toEqual(stack[4]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); - // else - expect(stack[4].command).toEqual(input[4]); - expect(stack[4].next).toBeUndefined(); - expect(stack[4].right).toEqual(stack[5]); - expect(stack[4].left).toEqual(stack[6]); // command + expect(stack[4].command).toEqual(input[4]); + expect(stack[4].next).toEqual(stack[7]); + expect(stack[4].right).toBeUndefined(); + expect(stack[4].left).toBeUndefined(); + // else expect(stack[5].command).toEqual(input[5]); expect(stack[5].next).toEqual(stack[6]); expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); - // end + // command expect(stack[6].command).toEqual(input[6]); - expect(stack[6].next).toBeUndefined(); + expect(stack[6].next).toEqual(stack[7]); expect(stack[6].right).toBeUndefined(); expect(stack[6].left).toBeUndefined(); + // end + expect(stack[7].command).toEqual(input[7]); + expect(stack[7].next).toBeUndefined(); + expect(stack[7].right).toBeUndefined(); + expect(stack[7].left).toBeUndefined(); }); - test.skip("do-command-while-end", () => { + test("do-command-while-end", () => { let input = [ { name: "do" }, { name: "command" }, @@ -277,7 +300,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._process(); + playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toEqual(stack[1]); @@ -289,7 +312,7 @@ describe("Control Flow", () => { expect(stack[1].left).toBeUndefined(); expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toBeUndefined(); - expect(stack[2].right).toEqual(stack[0]); + expect(stack[2].right).toEqual(stack[0]); // here expect(stack[2].left).toEqual(stack[3]); expect(stack[3].command).toEqual(input[3]); expect(stack[3].next).toBeUndefined(); From 1f17a29c307b4b008f0b2dd7a80b73694c57d41f Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 23:24:30 -0400 Subject: [PATCH 023/125] Added a dedicated test for checking that command nodes have the correct command references and removed this check from the other command node tests. Also added test coverage and implementation for some additional cases. --- .../selenium-ide/src/neo/IO/playback-tree.js | 6 +- .../src/neo/__test__/IO/playback-tree.spec.js | 100 +++++++++++++----- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree.js b/packages/selenium-ide/src/neo/IO/playback-tree.js index 69a237be1..29352ae1c 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree.js @@ -45,6 +45,10 @@ function isElse(command) { return (command.name === "else"); } +function isElseIf(command) { + return (command.name === "elseIf"); +} + function isEnd(command) { return (command.name === "end"); } @@ -252,7 +256,7 @@ export class PlaybackTree { } break; default: - if (isIf(this._topOfState()) && isControlFlowCommand(nextCommandNode.command)) { + if (isIf(this._topOfState()) && (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command) || isEnd(nextCommandNode.command))) { commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); } else if (isWhile(this._topOfState()) && isControlFlowCommand(nextCommandNode.command)) { commandNode.next = this._commandNodeStack[this._topOfState().index]; diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js index 591f0d989..c8f6bef5c 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js @@ -144,7 +144,7 @@ describe("Control Flow", () => { }); describe("Process", () => { describe("Linked List Validation", () => { - test("command-command", () => { + test("nodes contain command references", () => { let input = [ { name: "command1" }, { name: "command2" } @@ -155,6 +155,16 @@ describe("Control Flow", () => { let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[1].command).toEqual(input[1]); + }); + test("command-command", () => { + let input = [ + { name: "command1" }, + { name: "command2" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree._preprocessCommands(); + playbackTree._processCommands(); + let stack = playbackTree._commandNodeStack; expect(stack[0].next).toEqual(stack[1]); expect(stack[0].left).toBeUndefined(); expect(stack[0].right).toBeUndefined(); @@ -177,37 +187,30 @@ describe("Control Flow", () => { playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; // if - expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); // command - expect(stack[1].command).toEqual(input[1]); expect(stack[1].next).toEqual(stack[6]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); // elseIf - expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toEqual(stack[3]); expect(stack[2].left).toEqual(stack[4]); // command - expect(stack[3].command).toEqual(input[3]); expect(stack[3].next).toEqual(stack[6]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // else - expect(stack[4].command).toEqual(input[4]); expect(stack[4].next).toEqual(stack[5]); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // command - expect(stack[5].command).toEqual(input[5]); expect(stack[5].next).toEqual(stack[6]); expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); // end - expect(stack[6].command).toEqual(input[6]); expect(stack[6].next).toBeUndefined(); expect(stack[6].right).toBeUndefined(); expect(stack[6].left).toBeUndefined(); @@ -222,20 +225,55 @@ describe("Control Flow", () => { playbackTree._preprocessCommands(); playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; - expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); - expect(stack[1].command).toEqual(input[1]); expect(stack[1].next).toEqual(stack[0]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); - expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toBeUndefined(); expect(stack[2].left).toBeUndefined(); }); - test("if-while-command-end-else-command-end", () => { + test("if-command-while-command-end-end", () => { + let input = [ + { name: "if" }, + { name: "command" }, + { name: "while" }, + { name: "command" }, + { name: "end" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree._preprocessCommands(); + playbackTree._processCommands(); + let stack = playbackTree._commandNodeStack; + // if + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[5]); + // command + expect(stack[1].next).toEqual(stack[2]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + // while + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[3]); + expect(stack[2].left).toEqual(stack[4]); + // command + expect(stack[3].next).toEqual(stack[2]); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + // end + expect(stack[4].next).toEqual(stack[5]); + expect(stack[4].right).toBeUndefined(); + expect(stack[4].left).toBeUndefined(); + // end + expect(stack[5].next).toBeUndefined(); + expect(stack[5].right).toBeUndefined(); + expect(stack[5].left).toBeUndefined(); + }); + test("if-while-command-end-command-else-command-end", () => { let input = [ { name: "if" }, { name: "while" }, @@ -251,42 +289,34 @@ describe("Control Flow", () => { playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; // if - expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[5]); // while - expect(stack[1].command).toEqual(input[1]); expect(stack[1].next).toBeUndefined(); expect(stack[1].right).toEqual(stack[2]); expect(stack[1].left).toEqual(stack[3]); // command - expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toEqual(stack[1]); expect(stack[2].right).toBeUndefined(); expect(stack[2].left).toBeUndefined(); // end - expect(stack[3].command).toEqual(input[3]); expect(stack[3].next).toEqual(stack[4]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // command - expect(stack[4].command).toEqual(input[4]); expect(stack[4].next).toEqual(stack[7]); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // else - expect(stack[5].command).toEqual(input[5]); expect(stack[5].next).toEqual(stack[6]); expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); // command - expect(stack[6].command).toEqual(input[6]); expect(stack[6].next).toEqual(stack[7]); expect(stack[6].right).toBeUndefined(); expect(stack[6].left).toBeUndefined(); // end - expect(stack[7].command).toEqual(input[7]); expect(stack[7].next).toBeUndefined(); expect(stack[7].right).toBeUndefined(); expect(stack[7].left).toBeUndefined(); @@ -302,19 +332,39 @@ describe("Control Flow", () => { playbackTree._preprocessCommands(); playbackTree._processCommands(); let stack = playbackTree._commandNodeStack; - expect(stack[0].command).toEqual(input[0]); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); - expect(stack[1].command).toEqual(input[1]); expect(stack[1].next).toEqual(stack[2]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); - expect(stack[2].command).toEqual(input[2]); expect(stack[2].next).toBeUndefined(); - expect(stack[2].right).toEqual(stack[0]); // here + expect(stack[2].right).toEqual(stack[0]); + expect(stack[2].left).toEqual(stack[3]); + expect(stack[3].next).toBeUndefined(); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + }); + test.skip("do-command-repeatIf-end", () => { + let input = [ + { name: "do" }, + { name: "command" }, + { name: "repeatIf" }, + { name: "end" } + ]; + let playbackTree = new PlaybackTree(input); + playbackTree._preprocessCommands(); + playbackTree._processCommands(); + let stack = playbackTree._commandNodeStack; + expect(stack[0].next).toEqual(stack[1]); + expect(stack[0].right).toBeUndefined(); + expect(stack[0].left).toBeUndefined(); + expect(stack[1].next).toEqual(stack[2]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[0]); expect(stack[2].left).toEqual(stack[3]); - expect(stack[3].command).toEqual(input[3]); expect(stack[3].next).toBeUndefined(); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); From 4c538299b33f6d0f65c70b4eb93cc9f83d1ac20d Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Thu, 5 Jul 2018 23:26:17 -0400 Subject: [PATCH 024/125] Renamed #_processCommands to #_processCommandNodes --- .../selenium-ide/src/neo/IO/playback-tree.js | 2 +- .../src/neo/__test__/IO/playback-tree.spec.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/playback-tree.js b/packages/selenium-ide/src/neo/IO/playback-tree.js index 29352ae1c..65ee534d2 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree.js +++ b/packages/selenium-ide/src/neo/IO/playback-tree.js @@ -207,7 +207,7 @@ export class PlaybackTree { } } - _processCommands() { + _processCommandNodes() { this._state = []; let that = this; that._commandNodeStack.forEach(function(currentCommandNode, currentCommandNodeIndex) { diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js index c8f6bef5c..305c1db47 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js @@ -151,7 +151,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; expect(stack[0].command).toEqual(input[0]); expect(stack[1].command).toEqual(input[1]); @@ -163,7 +163,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; expect(stack[0].next).toEqual(stack[1]); expect(stack[0].left).toBeUndefined(); @@ -184,7 +184,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; // if expect(stack[0].next).toBeUndefined(); @@ -223,7 +223,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -246,7 +246,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; // if expect(stack[0].next).toBeUndefined(); @@ -286,7 +286,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; // if expect(stack[0].next).toBeUndefined(); @@ -330,7 +330,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); @@ -354,7 +354,7 @@ describe("Control Flow", () => { ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); - playbackTree._processCommands(); + playbackTree._processCommandNodes(); let stack = playbackTree._commandNodeStack; expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); From e646ffa35ae7000eae5957cb80b626a8d7e34427 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 10:29:23 -0400 Subject: [PATCH 025/125] Created a playback directory and moved playback-tree.js there. --- .../{IO => playback}/playback-tree.spec.js | 2 +- .../src/neo/{IO => playback}/playback-tree.js | 97 ++++++++++--------- 2 files changed, 50 insertions(+), 49 deletions(-) rename packages/selenium-ide/src/neo/__test__/{IO => playback}/playback-tree.spec.js (99%) rename packages/selenium-ide/src/neo/{IO => playback}/playback-tree.js (77%) diff --git a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js similarity index 99% rename from packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js rename to packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 305c1db47..70ff87205 100644 --- a/packages/selenium-ide/src/neo/__test__/IO/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { PlaybackTree } from "../../IO/playback-tree"; +import { PlaybackTree } from "../../playback/playback-tree"; describe("Control Flow", () => { describe("Preprocess", () => { diff --git a/packages/selenium-ide/src/neo/IO/playback-tree.js b/packages/selenium-ide/src/neo/playback/playback-tree.js similarity index 77% rename from packages/selenium-ide/src/neo/IO/playback-tree.js rename to packages/selenium-ide/src/neo/playback/playback-tree.js index 65ee534d2..91b099c13 100644 --- a/packages/selenium-ide/src/neo/IO/playback-tree.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree.js @@ -67,6 +67,23 @@ function isWhile(command) { return (command.name === "while"); } +function isEmpty(obj) { + if (obj) { + return (obj.length === 0); + } else { + return false; + } +} + +function topOf(array) { + let arr = array[array.length - 1]; + if (arr) { + return arr; + } else { + return { }; + } +} + export class PlaybackTree { constructor(commandStack) { this._commandStack = commandStack; @@ -84,8 +101,8 @@ export class PlaybackTree { that._currentCommandIndex = currentCommandIndex; that._preprocessCommand(); }); - if (!this._isStateEmpty()) { - throw "Incomplete block at " + this._topOfState().name; + if (!isEmpty(this._state)) { + throw "Incomplete block at " + topOf(this._state).name; } return true; } @@ -98,36 +115,36 @@ export class PlaybackTree { this._trackControlFlowBranchOpening(); break; case "while": - if (isDo(this._topOfState())) { + if (isDo(topOf(this._state))) { this._trackCommand(); } else { this._trackControlFlowBranchOpening(); } break; case "repeatIf": - if (!isDo(this._topOfState())) { + if (!isDo(topOf(this._state))) { throw "A repeatIf used without a do block"; } this._trackCommand(); break; case "else": case "elseIf": - if (!isIf(this._topOfState())) { + if (!isIf(topOf(this._state))) { throw "An else / elseIf used outside of an if block"; } this._trackControlFlowCommandElse(); break; case "end": - if (isLoop(this._topOfState())) { + if (isLoop(topOf(this._state))) { this._trackControlFlowBranchEnding(); - } else if (isIf(this._topOfState())) { + } else if (isIf(topOf(this._state))) { const numberOfElse = this._currentSegment().filter(command => isElse(command)).length; const allElseInCurrentSegment = this._currentSegment().filter(command => command.name.match(/else/)); if (numberOfElse > 1) { throw "Too many else commands used"; - } else if (numberOfElse === 1 && !isElse(this._topOf(allElseInCurrentSegment))) { + } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { throw "Incorrect command order of elseIf / else"; - } else if (numberOfElse === 0 || isElse(this._topOf(allElseInCurrentSegment))) { + } else if (numberOfElse === 0 || isElse(topOf(allElseInCurrentSegment))) { this._trackControlFlowBranchEnding(); } } else { @@ -140,32 +157,8 @@ export class PlaybackTree { } } - _createAndStoreCommandNode() { - let node = new CommandNode; - node.command = this._currentCommand; - node.level = this._level; - this._commandNodeStack.push(node); - } - - _topOf(segment) { - return segment[segment.length - 1]; - } - - _topOfState() { - let command = this._state[this._state.length - 1]; - if (command) { - return this._topOf(this._state); - } else { - return { name: "" }; - } - } - - _isStateEmpty() { - return (this._state.length === 0); - } - _currentSegment() { - return this._commandStack.slice(this._topOfState().index, this._currentCommandIndex); + return this._commandStack.slice(topOf(this._state).index, this._currentCommandIndex); } _trackControlFlowBranchOpening() { @@ -190,6 +183,13 @@ export class PlaybackTree { this._state.pop(); } + _createAndStoreCommandNode() { + let node = new CommandNode; + node.command = this._currentCommand; + node.level = this._level; + this._commandNodeStack.push(node); + } + _findNextNodeAtLevel(index, level) { for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { if (this._commandNodeStack[i].level === level) { @@ -208,23 +208,24 @@ export class PlaybackTree { } _processCommandNodes() { - this._state = []; let that = this; that._commandNodeStack.forEach(function(currentCommandNode, currentCommandNodeIndex) { that._processCommandNode(currentCommandNode, currentCommandNodeIndex); }); } + _processCommandNode(commandNode, commandNodeIndex) { + let state = this._state; let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; if (nextCommandNode) { switch(commandNode.command.name) { case "do": - this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); commandNode.next = nextCommandNode; break; case "if": - this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; @@ -236,30 +237,30 @@ export class PlaybackTree { commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; case "while": - if (isDo(this._topOfState())) { - commandNode.right = this._commandNodeStack[this._topOfState().index]; - commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); + if (isDo(topOf(state))) { + commandNode.right = this._commandNodeStack[topOf(state).index]; + commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); } else { - this._state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, commandNode.level); } break; case "end": - this._state.pop(); - if (!this._isStateEmpty()) { + state.pop(); + if (!isEmpty(state)) { if (isControlFlowCommand(nextCommandNode.command)) { - commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); + commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); } else { commandNode.next = nextCommandNode; } } break; default: - if (isIf(this._topOfState()) && (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command) || isEnd(nextCommandNode.command))) { - commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, this._topOfState().level); - } else if (isWhile(this._topOfState()) && isControlFlowCommand(nextCommandNode.command)) { - commandNode.next = this._commandNodeStack[this._topOfState().index]; + if (isIf(topOf(state)) && (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command) || isEnd(nextCommandNode.command))) { + commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); + } else if (topOf(state) && isWhile(topOf(state)) && isControlFlowCommand(nextCommandNode.command)) { + commandNode.next = this._commandNodeStack[topOf(state).index]; } else { commandNode.next = nextCommandNode; } From 46030efd2439d64c8a255c6a680803c7a08d110c Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 10:41:22 -0400 Subject: [PATCH 026/125] Switched from strings to constants when referencing a command name. --- .../src/neo/playback/playback-tree.js | 87 +++++++++++-------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree.js b/packages/selenium-ide/src/neo/playback/playback-tree.js index 91b099c13..25bf84146 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree.js @@ -16,55 +16,66 @@ // under the License. class CommandNode { - constructor() { - this.command; + constructor(command) { + this.command = command; this.next = undefined; this.left = undefined; this.right = undefined; this.level; - this.timesVisited; + this.timesVisited = 0; } } +const Command = { + if: "if", + else: "else", + elseIf: "elseIf", + times: "times", + while: "while", + do: "do", + repeatIf: "repeatIf", + end: "end" +}; + function isControlFlowCommand(command) { - return (command.name === "do" || - command.name === "else" || - command.name === "elseIf" || - command.name === "end" || - command.name === "if" || - command.name === "repeatIf" || - command.name === "times" || - command.name === "while"); + return (command.name === Command.do || + command.name === Command.else || + command.name === Command.elseIf || + command.name === Command.end || + command.name === Command.if || + command.name === Command.repeatIf || + command.name === Command.times || + command.name === Command.while); } function isDo(command) { - return (command.name === "do"); + return (command.name === Command.do); } function isElse(command) { - return (command.name === "else"); + return (command.name === Command.else); } function isElseIf(command) { - return (command.name === "elseIf"); + return (command.name === Command.elseIf); } function isEnd(command) { - return (command.name === "end"); + return (command.name === Command.end); } function isIf(command) { - return (command.name === "if"); + return (command.name === Command.if); } function isLoop(command) { - return (command.name === "while" || - command.name === "times" || - command.name === "do"); + return (command.name === Command.while || + command.name === Command.times || + command.name === Command.do); } function isWhile(command) { - return (command.name === "while"); + return (command.name === Command.while); } function isEmpty(obj) { @@ -109,37 +120,38 @@ export class PlaybackTree { _preprocessCommand() { switch (this._currentCommand.name) { - case "if": - case "do": - case "times": + case Command.if: + case Command.do: + case Command.times: this._trackControlFlowBranchOpening(); break; - case "while": + case Command.while: if (isDo(topOf(this._state))) { this._trackCommand(); } else { this._trackControlFlowBranchOpening(); } break; - case "repeatIf": + case Command.repeatIf: if (!isDo(topOf(this._state))) { throw "A repeatIf used without a do block"; } this._trackCommand(); break; - case "else": - case "elseIf": + case Command.else: + case Command.elseIf: if (!isIf(topOf(this._state))) { throw "An else / elseIf used outside of an if block"; } this._trackControlFlowCommandElse(); break; - case "end": + case Command.end: if (isLoop(topOf(this._state))) { this._trackControlFlowBranchEnding(); } else if (isIf(topOf(this._state))) { const numberOfElse = this._currentSegment().filter(command => isElse(command)).length; - const allElseInCurrentSegment = this._currentSegment().filter(command => command.name.match(/else/)); + const allElseInCurrentSegment = this._currentSegment().filter( + command => (command.name === Command.else || command.name === Command.elseIf)); if (numberOfElse > 1) { throw "Too many else commands used"; } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { @@ -184,8 +196,7 @@ export class PlaybackTree { } _createAndStoreCommandNode() { - let node = new CommandNode; - node.command = this._currentCommand; + let node = new CommandNode(this._currentCommand); node.level = this._level; this._commandNodeStack.push(node); } @@ -201,7 +212,7 @@ export class PlaybackTree { _findNextEndNodeAtLevel(index, level) { for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { if (this._commandNodeStack[i].level === level && - this._commandNodeStack[i].command.name === "end") { + this._commandNodeStack[i].command.name === Command.end) { return this._commandNodeStack[i]; } } @@ -220,23 +231,23 @@ export class PlaybackTree { let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; if (nextCommandNode) { switch(commandNode.command.name) { - case "do": + case Command.do: state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); commandNode.next = nextCommandNode; break; - case "if": + case Command.if: state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; - case "else": + case Command.else: commandNode.next = nextCommandNode; break; - case "elseIf": + case Command.elseIf: commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; - case "while": + case Command.while: if (isDo(topOf(state))) { commandNode.right = this._commandNodeStack[topOf(state).index]; commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); @@ -246,7 +257,7 @@ export class PlaybackTree { commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, commandNode.level); } break; - case "end": + case Command.end: state.pop(); if (!isEmpty(state)) { if (isControlFlowCommand(nextCommandNode.command)) { From bf2866dbde91c7c9b8f0c43ca6ff6a07826efba9 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 11:27:09 -0400 Subject: [PATCH 027/125] Switched to testing with Command objects and updated the playback tree to use the correct getter (e.g., command instead of name) --- .../__test__/playback/playback-tree.spec.js | 205 ++++++++++++------ .../src/neo/playback/playback-tree.js | 53 ++--- 2 files changed, 161 insertions(+), 97 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 70ff87205..a3654100c 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -16,27 +16,28 @@ // under the License. import { PlaybackTree } from "../../playback/playback-tree"; +import Command from "../../models/Command"; describe("Control Flow", () => { describe("Preprocess", () => { describe("Leveling", () => { test("returns leveled command stack", () => { let playbackTree = new PlaybackTree([ - { name: "if" }, - { name: "command" }, - { name: "else" }, - { name: "while" }, - { name: "command" }, - { name: "end" }, - { name: "do" }, - { name: "command" }, - { name: "while" }, - { name: "end" }, - { name: "do" }, - { name: "command" }, - { name: "repeatIf" }, - { name: "end" }, - { name: "end" } + new Command(null, "if", "", ""), + new Command(null, "command", "", ""), + new Command(null, "else", "", ""), + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "do", "", ""), + new Command(null, "command", "", ""), + new Command(null, "while", "", ""), + new Command(null, "end", "", ""), + new Command(null, "do", "", ""), + new Command(null, "command", "", ""), + new Command(null, "repeatIf", "", ""), + new Command(null, "end", "", ""), + new Command(null, "end", "", "") ]); playbackTree._preprocessCommands(); let stack = playbackTree._commandNodeStack; @@ -59,85 +60,147 @@ describe("Control Flow", () => { }); describe("Syntax Validation", () => { test("if, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, else, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, elseIf, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("if, elseIf, else, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "elseIf" }, { name: "else" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "else", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("while, end", () => { - let playbackTree = new PlaybackTree([{ name: "while" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "while", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("times, end", () => { - let playbackTree = new PlaybackTree([{ name: "times" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "times", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("do, repeatIf, end", () => { - let playbackTree = new PlaybackTree([{ name: "do" }, { name: "repeatIf" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "do", "", ""), + new Command(null, "repeatIf", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); test("do, while, end", () => { - let playbackTree = new PlaybackTree([{ name: "do" }, { name: "while" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "do", "", ""), + new Command(null, "while", "", ""), + new Command(null, "end", "", "") + ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); }); describe("Syntax Invalidation", () => { test("if", () => { - let playbackTree = new PlaybackTree([{ name: "if" }]); + let playbackTree = new PlaybackTree([new Command(null, "if", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "if" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "if", "", ""), + new Command(null, "end", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "end", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incorrect command order of elseIf / else"); }); test("if, else, else, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "else" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "else", "", ""), + new Command(null, "end", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Too many else commands used"); }); test("while", () => { - let playbackTree = new PlaybackTree([{ name: "while" }]); + let playbackTree = new PlaybackTree([new Command(null, "while", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); }); test("if, while", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "while", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "while", "", ""), + new Command(null, "end", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { - let playbackTree = new PlaybackTree([{ name: "if" }, { name: "else" }, { name: "elseIf" }, { name: "while" }, { name: "else" }, { name: "end" }]); + let playbackTree = new PlaybackTree([ + new Command(null, "if", "", ""), + new Command(null, "else", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "while", "", ""), + new Command(null, "else", "", ""), + new Command(null, "end", "", "") + ]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("An else / elseIf used outside of an if block"); }); test("times", () => { - let playbackTree = new PlaybackTree([{ name: "times" }]); + let playbackTree = new PlaybackTree([new Command(null, "times", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at times"); }); test("repeatIf", () => { - let playbackTree = new PlaybackTree([{ name: "repeatIf" }]); + let playbackTree = new PlaybackTree([new Command(null, "repeatIf", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("A repeatIf used without a do block"); }); test("do", () => { - let playbackTree = new PlaybackTree([{ name: "do" }]); + let playbackTree = new PlaybackTree([new Command(null, "do", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at do"); }); test("end", () => { - let playbackTree = new PlaybackTree([{ name: "end" }]); + let playbackTree = new PlaybackTree([new Command(null, "end", "", "")]); expect(function() { playbackTree._preprocessCommands(); }).toThrow("Use of end without an opening keyword"); }); }); @@ -146,8 +209,8 @@ describe("Control Flow", () => { describe("Linked List Validation", () => { test("nodes contain command references", () => { let input = [ - { name: "command1" }, - { name: "command2" } + new Command(null, "command1", "", ""), + new Command(null, "command2", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -158,8 +221,8 @@ describe("Control Flow", () => { }); test("command-command", () => { let input = [ - { name: "command1" }, - { name: "command2" } + new Command(null, "command1", "", ""), + new Command(null, "command2", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -174,13 +237,13 @@ describe("Control Flow", () => { }); test("if-command-elseIf-command-else-command-end", () => { let input = [ - { name: "if" }, - { name: "command" }, - { name: "elseIf" }, - { name: "command" }, - { name: "else" }, - { name: "command" }, - { name: "end" } + new Command(null, "if", "", ""), + new Command(null, "command", "", ""), + new Command(null, "elseIf", "", ""), + new Command(null, "command", "", ""), + new Command(null, "else", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -217,9 +280,9 @@ describe("Control Flow", () => { }); test("while-command-end", () => { let input = [ - { name: "while" }, - { name: "command" }, - { name: "end" } + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -237,12 +300,12 @@ describe("Control Flow", () => { }); test("if-command-while-command-end-end", () => { let input = [ - { name: "if" }, - { name: "command" }, - { name: "while" }, - { name: "command" }, - { name: "end" }, - { name: "end" } + new Command(null, "if", "", ""), + new Command(null, "command", "", ""), + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -275,14 +338,14 @@ describe("Control Flow", () => { }); test("if-while-command-end-command-else-command-end", () => { let input = [ - { name: "if" }, - { name: "while" }, - { name: "command" }, - { name: "end" }, - { name: "command" }, - { name: "else" }, - { name: "command" }, - { name: "end" } + new Command(null, "if", "", ""), + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "command", "", ""), + new Command(null, "else", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -323,10 +386,10 @@ describe("Control Flow", () => { }); test("do-command-while-end", () => { let input = [ - { name: "do" }, - { name: "command" }, - { name: "while" }, - { name: "end" } + new Command(null, "do", "", ""), + new Command(null, "command", "", ""), + new Command(null, "while", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -347,10 +410,10 @@ describe("Control Flow", () => { }); test.skip("do-command-repeatIf-end", () => { let input = [ - { name: "do" }, - { name: "command" }, - { name: "repeatIf" }, - { name: "end" } + new Command(null, "do", "", ""), + new Command(null, "command", "", ""), + new Command(null, "repeatIf", "", ""), + new Command(null, "end", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree.js b/packages/selenium-ide/src/neo/playback/playback-tree.js index 25bf84146..71e54df9b 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree.js @@ -38,44 +38,45 @@ const Command = { }; function isControlFlowCommand(command) { - return (command.name === Command.do || - command.name === Command.else || - command.name === Command.elseIf || - command.name === Command.end || - command.name === Command.if || - command.name === Command.repeatIf || - command.name === Command.times || - command.name === Command.while); + // #command is the command name + return (command.command === Command.do || + command.command === Command.else || + command.command === Command.elseIf || + command.command === Command.end || + command.command === Command.if || + command.command === Command.repeatIf || + command.command === Command.times || + command.command === Command.while); } function isDo(command) { - return (command.name === Command.do); + return (command.command === Command.do); } function isElse(command) { - return (command.name === Command.else); + return (command.command === Command.else); } function isElseIf(command) { - return (command.name === Command.elseIf); + return (command.command === Command.elseIf); } function isEnd(command) { - return (command.name === Command.end); + return (command.command === Command.end); } function isIf(command) { - return (command.name === Command.if); + return (command.command === Command.if); } function isLoop(command) { - return (command.name === Command.while || - command.name === Command.times || - command.name === Command.do); + return (command.command === Command.while || + command.command === Command.times || + command.command === Command.do); } function isWhile(command) { - return (command.name === Command.while); + return (command.command === Command.while); } function isEmpty(obj) { @@ -113,13 +114,13 @@ export class PlaybackTree { that._preprocessCommand(); }); if (!isEmpty(this._state)) { - throw "Incomplete block at " + topOf(this._state).name; + throw "Incomplete block at " + topOf(this._state).command; } return true; } _preprocessCommand() { - switch (this._currentCommand.name) { + switch (this._currentCommand.command) { case Command.if: case Command.do: case Command.times: @@ -151,7 +152,7 @@ export class PlaybackTree { } else if (isIf(topOf(this._state))) { const numberOfElse = this._currentSegment().filter(command => isElse(command)).length; const allElseInCurrentSegment = this._currentSegment().filter( - command => (command.name === Command.else || command.name === Command.elseIf)); + command => (command.command === Command.else || command.command === Command.elseIf)); if (numberOfElse > 1) { throw "Too many else commands used"; } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { @@ -174,7 +175,7 @@ export class PlaybackTree { } _trackControlFlowBranchOpening() { - this._state.push({ name: this._currentCommand.name, index: this._currentCommandIndex }); + this._state.push({ command: this._currentCommand.command, index: this._currentCommandIndex }); this._createAndStoreCommandNode(); this._level++; } @@ -212,7 +213,7 @@ export class PlaybackTree { _findNextEndNodeAtLevel(index, level) { for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { if (this._commandNodeStack[i].level === level && - this._commandNodeStack[i].command.name === Command.end) { + this._commandNodeStack[i].command.command === Command.end) { return this._commandNodeStack[i]; } } @@ -230,13 +231,13 @@ export class PlaybackTree { let state = this._state; let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; if (nextCommandNode) { - switch(commandNode.command.name) { + switch(commandNode.command.command) { case Command.do: - state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); commandNode.next = nextCommandNode; break; case Command.if: - state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; @@ -252,7 +253,7 @@ export class PlaybackTree { commandNode.right = this._commandNodeStack[topOf(state).index]; commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); } else { - state.push({ name: commandNode.command.name, level: commandNode.level, index: commandNodeIndex }); + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, commandNode.level); } From 725f7a7a3f13090898ab1e3b0f611e4514a8ac53 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 11:30:45 -0400 Subject: [PATCH 028/125] Renamed playback-tree.js to index.js and moved the CommandNode class into its own file --- .../playback/playback-tree/command-node.js | 27 +++++++++++++++++++ .../index.js} | 11 +------- 2 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/command-node.js rename packages/selenium-ide/src/neo/playback/{playback-tree.js => playback-tree/index.js} (97%) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js new file mode 100644 index 000000000..9eaf2d98f --- /dev/null +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -0,0 +1,27 @@ +// 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 class CommandNode { + constructor(command) { + this.command = command; + this.next = undefined; + this.left = undefined; + this.right = undefined; + this.level; + this.timesVisited = 0; + } +} diff --git a/packages/selenium-ide/src/neo/playback/playback-tree.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js similarity index 97% rename from packages/selenium-ide/src/neo/playback/playback-tree.js rename to packages/selenium-ide/src/neo/playback/playback-tree/index.js index 71e54df9b..fdb9b98fc 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -15,16 +15,7 @@ // specific language governing permissions and limitations // under the License. -class CommandNode { - constructor(command) { - this.command = command; - this.next = undefined; - this.left = undefined; - this.right = undefined; - this.level; - this.timesVisited = 0; - } -} +import { CommandNode } from "./command-node"; const Command = { if: "if", From 9e56ada528a6af1e1e9f25e2f5b6de70d10c1b45 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 12:44:26 -0400 Subject: [PATCH 029/125] Fixed do/repeatIf and while in the syntax tree --- .../__test__/playback/playback-tree.spec.js | 50 ++++++++++--------- .../src/neo/playback/playback-tree/index.js | 44 +++++++--------- 2 files changed, 44 insertions(+), 50 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index a3654100c..ef659ce1a 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -32,11 +32,9 @@ describe("Control Flow", () => { new Command(null, "do", "", ""), new Command(null, "command", "", ""), new Command(null, "while", "", ""), - new Command(null, "end", "", ""), - new Command(null, "do", "", ""), new Command(null, "command", "", ""), - new Command(null, "repeatIf", "", ""), new Command(null, "end", "", ""), + new Command(null, "repeatIf", "", ""), new Command(null, "end", "", "") ]); playbackTree._preprocessCommands(); @@ -50,12 +48,10 @@ describe("Control Flow", () => { expect(stack[6].level).toEqual(1); // do expect(stack[7].level).toEqual(2); // command expect(stack[8].level).toEqual(2); // while - expect(stack[9].level).toEqual(1); // end - expect(stack[10].level).toEqual(1); // do - expect(stack[11].level).toEqual(2); // command - expect(stack[12].level).toEqual(2); // repeatIf - expect(stack[13].level).toEqual(1); // end - expect(stack[14].level).toEqual(0); // end + expect(stack[9].level).toEqual(3); // command + expect(stack[10].level).toEqual(2); // end + expect(stack[11].level).toEqual(1); // repeatIf + expect(stack[12].level).toEqual(0); // end }); }); describe("Syntax Validation", () => { @@ -105,19 +101,19 @@ describe("Control Flow", () => { ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); - test("do, repeatIf, end", () => { + test("do, repeatIf", () => { let playbackTree = new PlaybackTree([ new Command(null, "do", "", ""), - new Command(null, "repeatIf", "", ""), - new Command(null, "end", "", "") + new Command(null, "repeatIf", "", "") ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); - test("do, while, end", () => { + test("do, while, end, repeatIf", () => { let playbackTree = new PlaybackTree([ new Command(null, "do", "", ""), new Command(null, "while", "", ""), - new Command(null, "end", "", "") + new Command(null, "end", "", ""), + new Command(null, "repeatIf", "", "") ]); expect(playbackTree._preprocessCommands()).toBeTruthy(); }); @@ -384,12 +380,12 @@ describe("Control Flow", () => { expect(stack[7].right).toBeUndefined(); expect(stack[7].left).toBeUndefined(); }); - test("do-command-while-end", () => { + test("do-command-repeatIf-end", () => { let input = [ new Command(null, "do", "", ""), new Command(null, "command", "", ""), - new Command(null, "while", "", ""), - new Command(null, "end", "", "") + new Command(null, "repeatIf", "", ""), + new Command(null, "command", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -408,12 +404,14 @@ describe("Control Flow", () => { expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); }); - test.skip("do-command-repeatIf-end", () => { + test("do-command-while-end-repeatIf", () => { let input = [ new Command(null, "do", "", ""), new Command(null, "command", "", ""), - new Command(null, "repeatIf", "", ""), - new Command(null, "end", "", "") + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "repeatIf", "", "") ]; let playbackTree = new PlaybackTree(input); playbackTree._preprocessCommands(); @@ -426,11 +424,17 @@ describe("Control Flow", () => { expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); expect(stack[2].next).toBeUndefined(); - expect(stack[2].right).toEqual(stack[0]); - expect(stack[2].left).toEqual(stack[3]); - expect(stack[3].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[3]); + expect(stack[2].left).toEqual(stack[4]); + expect(stack[3].next).toEqual(stack[2]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); + expect(stack[4].next).toEqual(stack[5]); + expect(stack[4].right).toBeUndefined(); + expect(stack[4].left).toBeUndefined(); + expect(stack[5].next).toBeUndefined(); + expect(stack[5].right).toBeUndefined(); + expect(stack[5].left).toBeUndefined(); }); }); }); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index fdb9b98fc..f68b26573 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -115,20 +115,8 @@ export class PlaybackTree { case Command.if: case Command.do: case Command.times: - this._trackControlFlowBranchOpening(); - break; case Command.while: - if (isDo(topOf(this._state))) { - this._trackCommand(); - } else { - this._trackControlFlowBranchOpening(); - } - break; - case Command.repeatIf: - if (!isDo(topOf(this._state))) { - throw "A repeatIf used without a do block"; - } - this._trackCommand(); + this._trackControlFlowBranchOpening(); break; case Command.else: case Command.elseIf: @@ -137,6 +125,12 @@ export class PlaybackTree { } this._trackControlFlowCommandElse(); break; + case Command.repeatIf: + if (!isDo(topOf(this._state))) { + throw "A repeatIf used without a do block"; + } + this._trackControlFlowBranchEnding(); + break; case Command.end: if (isLoop(topOf(this._state))) { this._trackControlFlowBranchEnding(); @@ -223,15 +217,16 @@ export class PlaybackTree { let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; if (nextCommandNode) { switch(commandNode.command.command) { - case Command.do: - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); - commandNode.next = nextCommandNode; - break; case Command.if: + case Command.while: state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; + case Command.do: + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); + commandNode.next = nextCommandNode; + break; case Command.else: commandNode.next = nextCommandNode; break; @@ -239,20 +234,15 @@ export class PlaybackTree { commandNode.right = nextCommandNode; commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); break; - case Command.while: - if (isDo(topOf(state))) { - commandNode.right = this._commandNodeStack[topOf(state).index]; - commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); - } else { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); - commandNode.right = nextCommandNode; - commandNode.left = this._findNextEndNodeAtLevel(commandNodeIndex, commandNode.level); - } + case Command.repeatIf: + commandNode.right = this._commandNodeStack[topOf(state).index]; + commandNode.left = nextCommandNode; + state.pop(); break; case Command.end: state.pop(); if (!isEmpty(state)) { - if (isControlFlowCommand(nextCommandNode.command)) { + if (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command)) { commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); } else { commandNode.next = nextCommandNode; From 941caebcc94c23065079570305dbccc83364ba3a Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 14:48:35 -0400 Subject: [PATCH 030/125] Moved syntax validation into more succinct functions outside of PlaybackTree. --- .../__test__/playback/playback-tree.spec.js | 4 +- .../src/neo/playback/playback-tree/index.js | 161 ++++++++++++------ 2 files changed, 112 insertions(+), 53 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index ef659ce1a..7bb8f5808 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -20,7 +20,7 @@ import Command from "../../models/Command"; describe("Control Flow", () => { describe("Preprocess", () => { - describe("Leveling", () => { + describe.skip("Leveling", () => { test("returns leveled command stack", () => { let playbackTree = new PlaybackTree([ new Command(null, "if", "", ""), @@ -201,7 +201,7 @@ describe("Control Flow", () => { }); }); }); - describe("Process", () => { + describe.skip("Process", () => { describe("Linked List Validation", () => { test("nodes contain command references", () => { let input = [ diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index f68b26573..107089839 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -87,103 +87,162 @@ function topOf(array) { } } +const verify = { + if: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + do: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + times: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + while: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + else: function (commandName, commandIndex, stack, state) { + if (!isIf(topOf(state))) { + throw "An else / elseIf used outside of an if block"; + } + }, + elseIf: function (commandName, commandIndex, stack, state) { + if (!isIf(topOf(state))) { + throw "An else / elseIf used outside of an if block"; + } + }, + repeatIf: function (commandName, commandIndex, stack, state) { + if (!isDo(topOf(state))) { + throw "A repeatIf used without a do block"; + } + state.pop(); + }, + end: function (commandName, commandIndex, stack, state) { + if (isLoop(topOf(state))) { + state.pop(); + } else if (isIf(topOf(state))) { + const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; + const allElseInCurrentSegment = stack.slice(topOf(state).index, commandIndex).filter( + command => (command.command === Command.else || command.command === Command.elseIf)); + if (numberOfElse > 1) { + throw "Too many else commands used"; + } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { + throw "Incorrect command order of elseIf / else"; + } else if (numberOfElse === 0 || isElse(topOf(allElseInCurrentSegment))) { + state.pop(); + } + } else { + throw "Use of end without an opening keyword"; + } + } +}; + +export function verifySyntax(commandStack) { + let state = []; + commandStack.forEach(function(command, commandIndex) { + if (verify[command.command]) { + verify[command.command](command.command, commandIndex, commandStack, state); + } + }); + if (!isEmpty(state)) { + throw "Incomplete block at " + topOf(state).command; + } else { + return true; + } +} + export class PlaybackTree { constructor(commandStack) { this._commandStack = commandStack; - this._state = []; - this._level = 0; - this._currentCommand; - this._currentCommandIndex; this._commandNodeStack = []; } _preprocessCommands() { - let that = this; - this._commandStack.forEach(function(currentCommand, currentCommandIndex) { - that._currentCommand = currentCommand; - that._currentCommandIndex = currentCommandIndex; - that._preprocessCommand(); - }); - if (!isEmpty(this._state)) { - throw "Incomplete block at " + topOf(this._state).command; - } - return true; + return verifySyntax(this._commandStack); + //let tracker = { state: [], level: 0 }; + //let that = this; + //this._commandStack.forEach(function(currentCommand, currentCommandIndex) { + // that._preprocessCommand(currentCommand, currentCommandIndex, tracker); + //}); + //return isStateEmpty(tracker.state); } - _preprocessCommand() { - switch (this._currentCommand.command) { + _preprocessCommand(currentCommand, currentCommandIndex, tracker) { + switch (currentCommand.command) { case Command.if: case Command.do: case Command.times: case Command.while: - this._trackControlFlowBranchOpening(); + this._trackControlFlowBranchOpening(currentCommand, currentCommandIndex, tracker); break; case Command.else: case Command.elseIf: - if (!isIf(topOf(this._state))) { + if (!isIf(topOf(tracker.state))) { throw "An else / elseIf used outside of an if block"; } - this._trackControlFlowCommandElse(); + this._trackControlFlowCommandElse(currentCommand, tracker); break; case Command.repeatIf: - if (!isDo(topOf(this._state))) { + if (!isDo(topOf(tracker.state))) { throw "A repeatIf used without a do block"; } - this._trackControlFlowBranchEnding(); + this._trackControlFlowBranchEnding(currentCommand, tracker); break; case Command.end: - if (isLoop(topOf(this._state))) { - this._trackControlFlowBranchEnding(); - } else if (isIf(topOf(this._state))) { - const numberOfElse = this._currentSegment().filter(command => isElse(command)).length; - const allElseInCurrentSegment = this._currentSegment().filter( - command => (command.command === Command.else || command.command === Command.elseIf)); + if (isLoop(topOf(tracker.state))) { + this._trackControlFlowBranchEnding(currentCommand, tracker); + } else if (isIf(topOf(tracker.state))) { + const numberOfElse = this._numberOfElseInSegment(this._commandStack, topOf(tracker.state).index, currentCommandIndex); + const allElseInCurrentSegment = this._allElseInSegment(this._commandStack, topOf(tracker.state).index, currentCommandIndex); if (numberOfElse > 1) { throw "Too many else commands used"; } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { throw "Incorrect command order of elseIf / else"; } else if (numberOfElse === 0 || isElse(topOf(allElseInCurrentSegment))) { - this._trackControlFlowBranchEnding(); + this._trackControlFlowBranchEnding(currentCommand, tracker); } } else { throw "Use of end without an opening keyword"; } break; default: - this._trackCommand(); + this._trackCommand(currentCommand, tracker); break; } } - _currentSegment() { - return this._commandStack.slice(topOf(this._state).index, this._currentCommandIndex); + _numberOfElseInSegment(stack, startingIndex, endingIndex) { + return stack.slice(startingIndex, endingIndex).filter(command => isElse(command)).length; + } + + _allElseInSegment(stack, startingIndex, endingIndex) { + return stack.slice(startingIndex, endingIndex).filter(command => (command.command === Command.else || command.command === Command.elseIf)); } - _trackControlFlowBranchOpening() { - this._state.push({ command: this._currentCommand.command, index: this._currentCommandIndex }); - this._createAndStoreCommandNode(); - this._level++; + _trackControlFlowBranchOpening(currentCommand, currentCommandIndex, tracker) { + tracker.state.push({ command: currentCommand.command, index: currentCommandIndex }); + this._createAndStoreCommandNode(currentCommand, tracker.level); + tracker.level++; } - _trackControlFlowCommandElse() { - this._level--; - this._createAndStoreCommandNode(); - this._level++; + _trackControlFlowCommandElse(currentCommand, tracker) { + tracker.level--; + this._createAndStoreCommandNode(currentCommand, tracker.level); + tracker.level++; } - _trackCommand() { - this._createAndStoreCommandNode(); + _trackCommand(currentCommand, tracker) { + this._createAndStoreCommandNode(currentCommand, tracker.level); } - _trackControlFlowBranchEnding() { - this._level--; - this._createAndStoreCommandNode(); - this._state.pop(); + _trackControlFlowBranchEnding(currentCommand, tracker) { + tracker.level--; + this._createAndStoreCommandNode(currentCommand, tracker.level); + tracker.state.pop(); } - _createAndStoreCommandNode() { - let node = new CommandNode(this._currentCommand); - node.level = this._level; + _createAndStoreCommandNode(currentCommand, level) { + let node = new CommandNode(currentCommand); + node.level = level; this._commandNodeStack.push(node); } @@ -205,15 +264,15 @@ export class PlaybackTree { } _processCommandNodes() { + let state = []; let that = this; that._commandNodeStack.forEach(function(currentCommandNode, currentCommandNodeIndex) { - that._processCommandNode(currentCommandNode, currentCommandNodeIndex); + that._processCommandNode(currentCommandNode, currentCommandNodeIndex, state); }); } - _processCommandNode(commandNode, commandNodeIndex) { - let state = this._state; + _processCommandNode(commandNode, commandNodeIndex, state) { let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; if (nextCommandNode) { switch(commandNode.command.command) { From 1ecbab86683cb8d180535f0a72573c750e91e444 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 8 Jul 2018 14:49:34 -0400 Subject: [PATCH 031/125] Removed pre-emptive export --- packages/selenium-ide/src/neo/playback/playback-tree/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 107089839..a9f7469dd 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -136,7 +136,7 @@ const verify = { } }; -export function verifySyntax(commandStack) { +function verifySyntax(commandStack) { let state = []; commandStack.forEach(function(command, commandIndex) { if (verify[command.command]) { From 92de2a90dae39ea6b66a427d61feb2dc600ab24e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 9 Jul 2018 10:51:39 -0400 Subject: [PATCH 032/125] Reworked playback-tree into just functions and added coverage for some additional test cases. --- .../__test__/playback/playback-tree.spec.js | 189 +++++---- .../playback/playback-tree/command-node.js | 1 + .../src/neo/playback/playback-tree/index.js | 382 ++++++++---------- 3 files changed, 280 insertions(+), 292 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 7bb8f5808..9465ba158 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -15,14 +15,14 @@ // specific language governing permissions and limitations // under the License. -import { PlaybackTree } from "../../playback/playback-tree"; +import { createPlaybackTree } from "../../playback/playback-tree"; import Command from "../../models/Command"; describe("Control Flow", () => { describe("Preprocess", () => { - describe.skip("Leveling", () => { + describe("Leveling", () => { test("returns leveled command stack", () => { - let playbackTree = new PlaybackTree([ + let stack = createPlaybackTree([ new Command(null, "if", "", ""), new Command(null, "command", "", ""), new Command(null, "else", "", ""), @@ -37,8 +37,6 @@ describe("Control Flow", () => { new Command(null, "repeatIf", "", ""), new Command(null, "end", "", "") ]); - playbackTree._preprocessCommands(); - let stack = playbackTree._commandNodeStack; expect(stack[0].level).toEqual(0); // if expect(stack[1].level).toEqual(1); // command expect(stack[2].level).toEqual(0); // else @@ -56,174 +54,167 @@ describe("Control Flow", () => { }); describe("Syntax Validation", () => { test("if, end", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "if", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("if, else, end", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("if, elseIf, end", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "if", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("if, elseIf, else, end", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "if", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "else", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("while, end", () => { - let playbackTree = new PlaybackTree([ + let result = new createPlaybackTree([ new Command(null, "while", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("times, end", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "times", "", ""), new Command(null, "end", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("do, repeatIf", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "do", "", ""), new Command(null, "repeatIf", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); test("do, while, end, repeatIf", () => { - let playbackTree = new PlaybackTree([ + let result = createPlaybackTree([ new Command(null, "do", "", ""), new Command(null, "while", "", ""), new Command(null, "end", "", ""), new Command(null, "repeatIf", "", "") ]); - expect(playbackTree._preprocessCommands()).toBeTruthy(); + expect(result).toBeTruthy(); }); }); describe("Syntax Invalidation", () => { test("if", () => { - let playbackTree = new PlaybackTree([new Command(null, "if", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); + let input = [new Command(null, "if", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "if", "", ""), new Command(null, "end", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); + ]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "end", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incorrect command order of elseIf / else"); + ]; + expect(function() { createPlaybackTree(input); }).toThrow("Incorrect command order of elseIf / else"); }); test("if, else, else, end", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "else", "", ""), new Command(null, "end", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Too many else commands used"); + ]; + expect(function() { createPlaybackTree(input); }).toThrow("Too many else commands used"); }); test("while", () => { - let playbackTree = new PlaybackTree([new Command(null, "while", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); + let input = [new Command(null, "while", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at while"); }); test("if, while", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "while", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at while"); + ]; + expect(function() {createPlaybackTree(input); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "while", "", ""), new Command(null, "end", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at if"); + ]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { - let playbackTree = new PlaybackTree([ + let input = [ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "while", "", ""), new Command(null, "else", "", ""), new Command(null, "end", "", "") - ]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("An else / elseIf used outside of an if block"); + ]; + expect(function() { createPlaybackTree(input); }).toThrow("An else / elseIf used outside of an if block"); }); test("times", () => { - let playbackTree = new PlaybackTree([new Command(null, "times", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at times"); + let input = [new Command(null, "times", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at times"); }); test("repeatIf", () => { - let playbackTree = new PlaybackTree([new Command(null, "repeatIf", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("A repeatIf used without a do block"); + let input = [new Command(null, "repeatIf", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("A repeatIf used without a do block"); }); test("do", () => { - let playbackTree = new PlaybackTree([new Command(null, "do", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Incomplete block at do"); + let input = [new Command(null, "do", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at do"); }); test("end", () => { - let playbackTree = new PlaybackTree([new Command(null, "end", "", "")]); - expect(function() { playbackTree._preprocessCommands(); }).toThrow("Use of end without an opening keyword"); + let input = [new Command(null, "end", "", "")]; + expect(function() { createPlaybackTree(input); }).toThrow("Use of end without an opening keyword"); }); }); }); - describe.skip("Process", () => { + describe("Process", () => { describe("Linked List Validation", () => { - test("nodes contain command references", () => { - let input = [ - new Command(null, "command1", "", ""), - new Command(null, "command2", "", "") - ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + test("nodes contain command references and levels", () => { + let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; + let stack = createPlaybackTree(input); expect(stack[0].command).toEqual(input[0]); + expect(stack[0].level).toEqual(0); expect(stack[1].command).toEqual(input[1]); + expect(stack[1].level).toEqual(0); }); test("command-command", () => { let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].left).toBeUndefined(); expect(stack[0].right).toBeUndefined(); @@ -241,10 +232,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -280,10 +268,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); @@ -294,6 +279,27 @@ describe("Control Flow", () => { expect(stack[2].right).toBeUndefined(); expect(stack[2].left).toBeUndefined(); }); + test("while-command-command-end", () => { + let input = [ + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", "") + ]; + let stack = createPlaybackTree(input); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[3]); + expect(stack[1].next).toEqual(stack[2]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].next).toEqual(stack[0]); + expect(stack[2].right).toBeUndefined(); + expect(stack[2].left).toBeUndefined(); + expect(stack[3].next).toBeUndefined(); + expect(stack[3].right).toBeUndefined(); + expect(stack[3].left).toBeUndefined(); + }); test("if-command-while-command-end-end", () => { let input = [ new Command(null, "if", "", ""), @@ -303,10 +309,7 @@ describe("Control Flow", () => { new Command(null, "end", "", ""), new Command(null, "end", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -343,10 +346,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -387,10 +387,7 @@ describe("Control Flow", () => { new Command(null, "repeatIf", "", ""), new Command(null, "command", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); @@ -413,10 +410,7 @@ describe("Control Flow", () => { new Command(null, "end", "", ""), new Command(null, "repeatIf", "", "") ]; - let playbackTree = new PlaybackTree(input); - playbackTree._preprocessCommands(); - playbackTree._processCommandNodes(); - let stack = playbackTree._commandNodeStack; + let stack = createPlaybackTree(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); @@ -436,6 +430,23 @@ describe("Control Flow", () => { expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); }); + test("times-command-end", () => { + let input = [ + new Command(null, "times", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", "") + ]; + let stack = createPlaybackTree(input); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toEqual(stack[2]); + expect(stack[1].next).toEqual(stack[0]); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toBeUndefined(); + expect(stack[2].left).toBeUndefined(); + }); }); }); }); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 9eaf2d98f..300ed611f 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -21,6 +21,7 @@ export class CommandNode { this.next = undefined; this.left = undefined; this.right = undefined; + this.index; this.level; this.timesVisited = 0; } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index a9f7469dd..edc1f64fc 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -17,57 +17,48 @@ import { CommandNode } from "./command-node"; -const Command = { - if: "if", +export function createPlaybackTree(commandStack) { + verifyControlFlowSyntax(commandStack); + let levels = deriveCommandLevels(commandStack); + let nodes = initCommandNodes(commandStack, levels); + return connectCommandNodes(nodes); +} + +const CommandName = { + do: "do", else: "else", elseIf: "elseIf", - times: "times", - while: "while", - do: "do", + end: "end", + if: "if", repeatIf: "repeatIf", - end: "end" + times: "times", + while: "while" }; -function isControlFlowCommand(command) { - // #command is the command name - return (command.command === Command.do || - command.command === Command.else || - command.command === Command.elseIf || - command.command === Command.end || - command.command === Command.if || - command.command === Command.repeatIf || - command.command === Command.times || - command.command === Command.while); +function isElseOrElseIf(command) { + return (command.command === CommandName.else || + command.command === CommandName.elseIf); } function isDo(command) { - return (command.command === Command.do); + return (command.command === CommandName.do); } function isElse(command) { - return (command.command === Command.else); -} - -function isElseIf(command) { - return (command.command === Command.elseIf); + return (command.command === CommandName.else); } function isEnd(command) { - return (command.command === Command.end); + return (command.command === CommandName.end); } function isIf(command) { - return (command.command === Command.if); + return (command.command === CommandName.if); } function isLoop(command) { - return (command.command === Command.while || - command.command === Command.times || - command.command === Command.do); -} - -function isWhile(command) { - return (command.command === Command.while); + return (command.command === CommandName.while || + command.command === CommandName.times); } function isEmpty(obj) { @@ -87,7 +78,21 @@ function topOf(array) { } } -const verify = { +function verifyControlFlowSyntax(commandStack) { + let state = []; + commandStack.forEach(function(command, commandIndex) { + if (verifyCommand[command.command]) { + verifyCommand[command.command](command.command, commandIndex, commandStack, state); + } + }); + if (!isEmpty(state)) { + throw "Incomplete block at " + topOf(state).command; + } else { + return true; + } +} + +const verifyCommand = { if: function (commandName, commandIndex, stack, state) { state.push({ command: commandName, index: commandIndex }); }, @@ -121,13 +126,13 @@ const verify = { state.pop(); } else if (isIf(topOf(state))) { const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; - const allElseInCurrentSegment = stack.slice(topOf(state).index, commandIndex).filter( - command => (command.command === Command.else || command.command === Command.elseIf)); + const allElses = stack.slice(topOf(state).index, commandIndex).filter( + command => (command.command === CommandName.else || command.command === CommandName.elseIf)); if (numberOfElse > 1) { throw "Too many else commands used"; - } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { + } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { throw "Incorrect command order of elseIf / else"; - } else if (numberOfElse === 0 || isElse(topOf(allElseInCurrentSegment))) { + } else if (numberOfElse === 0 || isElse(topOf(allElses))) { state.pop(); } } else { @@ -136,187 +141,158 @@ const verify = { } }; -function verifySyntax(commandStack) { - let state = []; - commandStack.forEach(function(command, commandIndex) { - if (verify[command.command]) { - verify[command.command](command.command, commandIndex, commandStack, state); +function deriveCommandLevels(commandStack) { + let level = 0; + let levels = []; + commandStack.forEach(function(command) { + if (levelCommand[command.command]) { + level = levelCommand[command.command](command, level, levels); + } else { + levelCommand["default"](command, level, levels); } }); - if (!isEmpty(state)) { - throw "Incomplete block at " + topOf(state).command; - } else { - return true; - } + return levels; } -export class PlaybackTree { - constructor(commandStack) { - this._commandStack = commandStack; - this._commandNodeStack = []; - } - - _preprocessCommands() { - return verifySyntax(this._commandStack); - //let tracker = { state: [], level: 0 }; - //let that = this; - //this._commandStack.forEach(function(currentCommand, currentCommandIndex) { - // that._preprocessCommand(currentCommand, currentCommandIndex, tracker); - //}); - //return isStateEmpty(tracker.state); - } - - _preprocessCommand(currentCommand, currentCommandIndex, tracker) { - switch (currentCommand.command) { - case Command.if: - case Command.do: - case Command.times: - case Command.while: - this._trackControlFlowBranchOpening(currentCommand, currentCommandIndex, tracker); - break; - case Command.else: - case Command.elseIf: - if (!isIf(topOf(tracker.state))) { - throw "An else / elseIf used outside of an if block"; - } - this._trackControlFlowCommandElse(currentCommand, tracker); - break; - case Command.repeatIf: - if (!isDo(topOf(tracker.state))) { - throw "A repeatIf used without a do block"; - } - this._trackControlFlowBranchEnding(currentCommand, tracker); - break; - case Command.end: - if (isLoop(topOf(tracker.state))) { - this._trackControlFlowBranchEnding(currentCommand, tracker); - } else if (isIf(topOf(tracker.state))) { - const numberOfElse = this._numberOfElseInSegment(this._commandStack, topOf(tracker.state).index, currentCommandIndex); - const allElseInCurrentSegment = this._allElseInSegment(this._commandStack, topOf(tracker.state).index, currentCommandIndex); - if (numberOfElse > 1) { - throw "Too many else commands used"; - } else if (numberOfElse === 1 && !isElse(topOf(allElseInCurrentSegment))) { - throw "Incorrect command order of elseIf / else"; - } else if (numberOfElse === 0 || isElse(topOf(allElseInCurrentSegment))) { - this._trackControlFlowBranchEnding(currentCommand, tracker); - } - } else { - throw "Use of end without an opening keyword"; - } - break; - default: - this._trackCommand(currentCommand, tracker); - break; - } - } - - _numberOfElseInSegment(stack, startingIndex, endingIndex) { - return stack.slice(startingIndex, endingIndex).filter(command => isElse(command)).length; - } - - _allElseInSegment(stack, startingIndex, endingIndex) { - return stack.slice(startingIndex, endingIndex).filter(command => (command.command === Command.else || command.command === Command.elseIf)); - } - - _trackControlFlowBranchOpening(currentCommand, currentCommandIndex, tracker) { - tracker.state.push({ command: currentCommand.command, index: currentCommandIndex }); - this._createAndStoreCommandNode(currentCommand, tracker.level); - tracker.level++; - } - - _trackControlFlowCommandElse(currentCommand, tracker) { - tracker.level--; - this._createAndStoreCommandNode(currentCommand, tracker.level); - tracker.level++; - } - - _trackCommand(currentCommand, tracker) { - this._createAndStoreCommandNode(currentCommand, tracker.level); - } - - _trackControlFlowBranchEnding(currentCommand, tracker) { - tracker.level--; - this._createAndStoreCommandNode(currentCommand, tracker.level); - tracker.state.pop(); +let levelCommand = { + if: function (command, level, levels) { + levels.push(level); + level++; + return level; + }, + do: function (command, level, levels) { + levels.push(level); + level++; + return level; + }, + times: function (command, level, levels) { + levels.push(level); + level++; + return level; + }, + while: function (command, level, levels) { + levels.push(level); + level++; + return level; + }, + else: function (command, level, levels) { + level--; + levels.push(level); + level++; + return level; + }, + elseIf: function (command, level, levels) { + level--; + levels.push(level); + level++; + return level; + }, + repeatIf: function (command, level, levels) { + level--; + levels.push(level); + return level; + }, + end: function (command, level, levels) { + level--; + levels.push(level); + return level; + }, + default: function (command, level, levels) { + levels.push(level); + return level; } +}; - _createAndStoreCommandNode(currentCommand, level) { - let node = new CommandNode(currentCommand); - node.level = level; - this._commandNodeStack.push(node); - } +function initCommandNodes(commandStack, levels) { + let commandNodes = []; + commandStack.forEach(function(command, index) { + let node = new CommandNode(command); + node.index = index; + node.level = levels[index]; + commandNodes.push(node); + }); + return commandNodes; +} - _findNextNodeAtLevel(index, level) { - for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { - if (this._commandNodeStack[i].level === level) { - return this._commandNodeStack[i]; +function connectCommandNodes(commandNodeStack) { + let _commandNodeStack = [ ...commandNodeStack ]; + let state = []; + _commandNodeStack.forEach(function(commandNode) { + let nextCommandNode = _commandNodeStack[commandNode.index + 1]; + if (nextCommandNode) { + if (connectCommandNode[commandNode.command.command]) { + connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, _commandNodeStack, state); + } else { + connectCommandNode["default"](commandNode, nextCommandNode, _commandNodeStack, state); } } - } + }); + return _commandNodeStack; +} - _findNextEndNodeAtLevel(index, level) { - for(let i = index + 1; i < this._commandNodeStack.length + 1; i++) { - if (this._commandNodeStack[i].level === level && - this._commandNodeStack[i].command.command === Command.end) { - return this._commandNodeStack[i]; +let connectCommandNode = { + default: function (commandNode, nextCommandNode, stack, state) { + if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { + commandNode.next = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { + commandNode.next = stack[topOf(state).index]; + } else { + commandNode.next = nextCommandNode; + } + }, + do: function (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + commandNode.next = nextCommandNode; + }, + else: function (commandNode, nextCommandNode) { + commandNode.next = nextCommandNode; + }, + elseIf: function (commandNode, nextCommandNode, stack) { + commandNode.right = nextCommandNode; + commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + }, + end: function (commandNode, nextCommandNode, stack, state) { + state.pop(); + if (!isEmpty(state)) { + if (isElseOrElseIf(nextCommandNode.command)) { + commandNode.next = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + } else { + commandNode.next = nextCommandNode; } } + }, + if: function (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + commandNode.right = nextCommandNode; + commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + }, + repeatIf: function (commandNode, nextCommandNode, stack, state) { + commandNode.right = stack[topOf(state).index]; + commandNode.left = nextCommandNode; + state.pop(); + }, + times: function (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + commandNode.right = nextCommandNode; + commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + }, + while: function (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + commandNode.right = nextCommandNode; + commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); } +}; - _processCommandNodes() { - let state = []; - let that = this; - that._commandNodeStack.forEach(function(currentCommandNode, currentCommandNodeIndex) { - that._processCommandNode(currentCommandNode, currentCommandNodeIndex, state); - }); - } - - - _processCommandNode(commandNode, commandNodeIndex, state) { - let nextCommandNode = this._commandNodeStack[commandNodeIndex + 1]; - if (nextCommandNode) { - switch(commandNode.command.command) { - case Command.if: - case Command.while: - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); - commandNode.right = nextCommandNode; - commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); - break; - case Command.do: - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNodeIndex }); - commandNode.next = nextCommandNode; - break; - case Command.else: - commandNode.next = nextCommandNode; - break; - case Command.elseIf: - commandNode.right = nextCommandNode; - commandNode.left = this._findNextNodeAtLevel(commandNodeIndex, commandNode.level); - break; - case Command.repeatIf: - commandNode.right = this._commandNodeStack[topOf(state).index]; - commandNode.left = nextCommandNode; - state.pop(); - break; - case Command.end: - state.pop(); - if (!isEmpty(state)) { - if (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command)) { - commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); - } else { - commandNode.next = nextCommandNode; - } - } - break; - default: - if (isIf(topOf(state)) && (isElse(nextCommandNode.command) || isElseIf(nextCommandNode.command) || isEnd(nextCommandNode.command))) { - commandNode.next = this._findNextEndNodeAtLevel(commandNodeIndex, topOf(state).level); - } else if (topOf(state) && isWhile(topOf(state)) && isControlFlowCommand(nextCommandNode.command)) { - commandNode.next = this._commandNodeStack[topOf(state).index]; - } else { - commandNode.next = nextCommandNode; - } - break; +function findNextNodeBy(stack, index, level, commandName) { + for(let i = index + 1; i < stack.length + 1; i++) { + if (commandName) { + if (stack[i].level === level && + stack[i].command.command === commandName) { + return stack[i]; + } + } else { + if (stack[i].level === level) { + return stack[i]; } } } From 548fccffa1ff29e2ec49f68d9477a46683c6419e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 9 Jul 2018 11:21:08 -0400 Subject: [PATCH 033/125] Reordered functions to be alphabetical by object key --- .../src/neo/playback/playback-tree/index.js | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index edc1f64fc..e4410910b 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -35,11 +35,6 @@ const CommandName = { while: "while" }; -function isElseOrElseIf(command) { - return (command.command === CommandName.else || - command.command === CommandName.elseIf); -} - function isDo(command) { return (command.command === CommandName.do); } @@ -48,6 +43,11 @@ function isElse(command) { return (command.command === CommandName.else); } +function isElseOrElseIf(command) { + return (command.command === CommandName.else || + command.command === CommandName.elseIf); +} + function isEnd(command) { return (command.command === CommandName.end); } @@ -93,18 +93,9 @@ function verifyControlFlowSyntax(commandStack) { } const verifyCommand = { - if: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); - }, do: function (commandName, commandIndex, stack, state) { state.push({ command: commandName, index: commandIndex }); }, - times: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); - }, - while: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); - }, else: function (commandName, commandIndex, stack, state) { if (!isIf(topOf(state))) { throw "An else / elseIf used outside of an if block"; @@ -115,12 +106,6 @@ const verifyCommand = { throw "An else / elseIf used outside of an if block"; } }, - repeatIf: function (commandName, commandIndex, stack, state) { - if (!isDo(topOf(state))) { - throw "A repeatIf used without a do block"; - } - state.pop(); - }, end: function (commandName, commandIndex, stack, state) { if (isLoop(topOf(state))) { state.pop(); @@ -138,6 +123,21 @@ const verifyCommand = { } else { throw "Use of end without an opening keyword"; } + }, + if: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + repeatIf: function (commandName, commandIndex, stack, state) { + if (!isDo(topOf(state))) { + throw "A repeatIf used without a do block"; + } + state.pop(); + }, + times: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); + }, + while: function (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); } }; @@ -155,9 +155,8 @@ function deriveCommandLevels(commandStack) { } let levelCommand = { - if: function (command, level, levels) { + default: function (command, level, levels) { levels.push(level); - level++; return level; }, do: function (command, level, levels) { @@ -165,24 +164,24 @@ let levelCommand = { level++; return level; }, - times: function (command, level, levels) { + else: function (command, level, levels) { + level--; levels.push(level); level++; return level; }, - while: function (command, level, levels) { + elseIf: function (command, level, levels) { + level--; levels.push(level); level++; return level; }, - else: function (command, level, levels) { + end: function (command, level, levels) { level--; levels.push(level); - level++; return level; }, - elseIf: function (command, level, levels) { - level--; + if: function (command, level, levels) { levels.push(level); level++; return level; @@ -192,13 +191,14 @@ let levelCommand = { levels.push(level); return level; }, - end: function (command, level, levels) { - level--; + times: function (command, level, levels) { levels.push(level); + level++; return level; }, - default: function (command, level, levels) { + while: function (command, level, levels) { levels.push(level); + level++; return level; } }; From d611c314606ca98826c3e35b20529d00e0d36239 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 9 Jul 2018 14:00:54 -0400 Subject: [PATCH 034/125] Broke repeated procedures out into their own unique functions. Also exported two more functions from playback-tree for more targeted testing. --- .../__test__/playback/playback-tree.spec.js | 80 +++++----- .../src/neo/playback/playback-tree/index.js | 138 +++++++++++------- 2 files changed, 128 insertions(+), 90 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 9465ba158..793ac9f95 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -15,14 +15,14 @@ // specific language governing permissions and limitations // under the License. -import { createPlaybackTree } from "../../playback/playback-tree"; +import { deriveCommandLevels, verifyControlFlowSyntax, createPlaybackTree } from "../../playback/playback-tree"; import Command from "../../models/Command"; describe("Control Flow", () => { describe("Preprocess", () => { describe("Leveling", () => { test("returns leveled command stack", () => { - let stack = createPlaybackTree([ + let stack = deriveCommandLevels([ new Command(null, "if", "", ""), new Command(null, "command", "", ""), new Command(null, "else", "", ""), @@ -37,31 +37,31 @@ describe("Control Flow", () => { new Command(null, "repeatIf", "", ""), new Command(null, "end", "", "") ]); - expect(stack[0].level).toEqual(0); // if - expect(stack[1].level).toEqual(1); // command - expect(stack[2].level).toEqual(0); // else - expect(stack[3].level).toEqual(1); // while - expect(stack[4].level).toEqual(2); // command - expect(stack[5].level).toEqual(1); // end - expect(stack[6].level).toEqual(1); // do - expect(stack[7].level).toEqual(2); // command - expect(stack[8].level).toEqual(2); // while - expect(stack[9].level).toEqual(3); // command - expect(stack[10].level).toEqual(2); // end - expect(stack[11].level).toEqual(1); // repeatIf - expect(stack[12].level).toEqual(0); // end + expect(stack[0]).toEqual(0); // if + expect(stack[1]).toEqual(1); // command + expect(stack[2]).toEqual(0); // else + expect(stack[3]).toEqual(1); // while + expect(stack[4]).toEqual(2); // command + expect(stack[5]).toEqual(1); // end + expect(stack[6]).toEqual(1); // do + expect(stack[7]).toEqual(2); // command + expect(stack[8]).toEqual(2); // while + expect(stack[9]).toEqual(3); // command + expect(stack[10]).toEqual(2); // end + expect(stack[11]).toEqual(1); // repeatIf + expect(stack[12]).toEqual(0); // end }); }); describe("Syntax Validation", () => { test("if, end", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "if", "", ""), new Command(null, "end", "", "") ]); expect(result).toBeTruthy(); }); test("if, else, end", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "if", "", ""), new Command(null, "else", "", ""), new Command(null, "end", "", "") @@ -69,7 +69,7 @@ describe("Control Flow", () => { expect(result).toBeTruthy(); }); test("if, elseIf, end", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "if", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "end", "", "") @@ -77,7 +77,7 @@ describe("Control Flow", () => { expect(result).toBeTruthy(); }); test("if, elseIf, else, end", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "if", "", ""), new Command(null, "elseIf", "", ""), new Command(null, "else", "", ""), @@ -86,28 +86,28 @@ describe("Control Flow", () => { expect(result).toBeTruthy(); }); test("while, end", () => { - let result = new createPlaybackTree([ + let result = new verifyControlFlowSyntax([ new Command(null, "while", "", ""), new Command(null, "end", "", "") ]); expect(result).toBeTruthy(); }); test("times, end", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "times", "", ""), new Command(null, "end", "", "") ]); expect(result).toBeTruthy(); }); test("do, repeatIf", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "do", "", ""), new Command(null, "repeatIf", "", "") ]); expect(result).toBeTruthy(); }); test("do, while, end, repeatIf", () => { - let result = createPlaybackTree([ + let result = verifyControlFlowSyntax([ new Command(null, "do", "", ""), new Command(null, "while", "", ""), new Command(null, "end", "", ""), @@ -119,7 +119,7 @@ describe("Control Flow", () => { describe("Syntax Invalidation", () => { test("if", () => { let input = [new Command(null, "if", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { let input = [ @@ -127,7 +127,7 @@ describe("Control Flow", () => { new Command(null, "if", "", ""), new Command(null, "end", "", "") ]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { let input = [ @@ -136,7 +136,7 @@ describe("Control Flow", () => { new Command(null, "elseIf", "", ""), new Command(null, "end", "", "") ]; - expect(function() { createPlaybackTree(input); }).toThrow("Incorrect command order of elseIf / else"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incorrect command order of elseIf / else"); }); test("if, else, else, end", () => { let input = [ @@ -145,11 +145,11 @@ describe("Control Flow", () => { new Command(null, "else", "", ""), new Command(null, "end", "", "") ]; - expect(function() { createPlaybackTree(input); }).toThrow("Too many else commands used"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Too many else commands used"); }); test("while", () => { let input = [new Command(null, "while", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at while"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); }); test("if, while", () => { let input = [ @@ -158,7 +158,7 @@ describe("Control Flow", () => { new Command(null, "elseIf", "", ""), new Command(null, "while", "", "") ]; - expect(function() {createPlaybackTree(input); }).toThrow("Incomplete block at while"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { let input = [ @@ -168,7 +168,7 @@ describe("Control Flow", () => { new Command(null, "while", "", ""), new Command(null, "end", "", "") ]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at if"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { let input = [ @@ -179,23 +179,23 @@ describe("Control Flow", () => { new Command(null, "else", "", ""), new Command(null, "end", "", "") ]; - expect(function() { createPlaybackTree(input); }).toThrow("An else / elseIf used outside of an if block"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else / elseIf used outside of an if block"); }); test("times", () => { let input = [new Command(null, "times", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at times"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at times"); }); test("repeatIf", () => { let input = [new Command(null, "repeatIf", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("A repeatIf used without a do block"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("A repeatIf used without a do block"); }); test("do", () => { let input = [new Command(null, "do", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("Incomplete block at do"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at do"); }); test("end", () => { let input = [new Command(null, "end", "", "")]; - expect(function() { createPlaybackTree(input); }).toThrow("Use of end without an opening keyword"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Use of end without an opening keyword"); }); }); }); @@ -203,11 +203,11 @@ describe("Control Flow", () => { describe("Linked List Validation", () => { test("nodes contain command references and levels", () => { let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; - let stack = createPlaybackTree(input); - expect(stack[0].command).toEqual(input[0]); - expect(stack[0].level).toEqual(0); - expect(stack[1].command).toEqual(input[1]); - expect(stack[1].level).toEqual(0); + let automata = createPlaybackTree(input); + expect(automata[0].command).toEqual(input[0]); + expect(automata[0].level).toEqual(0); + expect(automata[1].command).toEqual(input[1]); + expect(automata[1].level).toEqual(0); }); test("command-command", () => { let input = [ diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index e4410910b..9cf3aad9b 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -16,12 +16,23 @@ // under the License. import { CommandNode } from "./command-node"; +export { createPlaybackTree, verifyControlFlowSyntax, deriveCommandLevels, connectCommandNodes }; -export function createPlaybackTree(commandStack) { +function createPlaybackTree(commandStack) { verifyControlFlowSyntax(commandStack); let levels = deriveCommandLevels(commandStack); - let nodes = initCommandNodes(commandStack, levels); - return connectCommandNodes(nodes); + let initNodes = initCommandNodes(commandStack, levels); + return connectCommandNodes(initNodes); + //Automata.startingCommandNode = nodes[0]; + //Automata.currentCommandNode = nodes[0]; + //return Automata; +} + +class Automata { + constructor() { + this.startingCommandNode; + this.currentCommandNode; + } } const CommandName = { @@ -94,53 +105,67 @@ function verifyControlFlowSyntax(commandStack) { const verifyCommand = { do: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); + trackControlFlowBranchOpen(commandName, commandIndex, stack, state); }, else: function (commandName, commandIndex, stack, state) { - if (!isIf(topOf(state))) { - throw "An else / elseIf used outside of an if block"; - } + verifyElse(commandName, commandIndex, stack, state); }, elseIf: function (commandName, commandIndex, stack, state) { - if (!isIf(topOf(state))) { - throw "An else / elseIf used outside of an if block"; - } + verifyElse(commandName, commandIndex, stack, state); }, end: function (commandName, commandIndex, stack, state) { - if (isLoop(topOf(state))) { - state.pop(); - } else if (isIf(topOf(state))) { - const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; - const allElses = stack.slice(topOf(state).index, commandIndex).filter( - command => (command.command === CommandName.else || command.command === CommandName.elseIf)); - if (numberOfElse > 1) { - throw "Too many else commands used"; - } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { - throw "Incorrect command order of elseIf / else"; - } else if (numberOfElse === 0 || isElse(topOf(allElses))) { - state.pop(); - } - } else { - throw "Use of end without an opening keyword"; - } + verifyEnd(commandName, commandIndex, stack, state); }, if: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); + trackControlFlowBranchOpen(commandName, commandIndex, stack, state); }, repeatIf: function (commandName, commandIndex, stack, state) { - if (!isDo(topOf(state))) { - throw "A repeatIf used without a do block"; - } - state.pop(); + verifyRepeatIf(commandName, commandIndex, stack, state); }, times: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); + trackControlFlowBranchOpen(commandName, commandIndex, stack, state); }, while: function (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); + trackControlFlowBranchOpen(commandName, commandIndex, stack, state); } }; +function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { + state.push({ command: commandName, index: commandIndex }); +} + +function verifyElse (commandName, commandIndex, stack, state) { + if (!isIf(topOf(state))) { + throw "An else / elseIf used outside of an if block"; + } +} + +function verifyEnd (commandName, commandIndex, stack, state) { + if (isLoop(topOf(state))) { + state.pop(); + } else if (isIf(topOf(state))) { + const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; + const allElses = stack.slice(topOf(state).index, commandIndex).filter( + command => (command.command === CommandName.else || command.command === CommandName.elseIf)); + if (numberOfElse > 1) { + throw "Too many else commands used"; + } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { + throw "Incorrect command order of elseIf / else"; + } else if (numberOfElse === 0 || isElse(topOf(allElses))) { + state.pop(); + } + } else { + throw "Use of end without an opening keyword"; + } +} + +function verifyRepeatIf (commandName, commandIndex, stack, state) { + if (!isDo(topOf(state))) { + throw "A repeatIf used without a do block"; + } + state.pop(); +} + function deriveCommandLevels(commandStack) { let level = 0; let levels = []; @@ -232,57 +257,70 @@ function connectCommandNodes(commandNodeStack) { let connectCommandNode = { default: function (commandNode, nextCommandNode, stack, state) { + let _nextCommandNode; if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { - commandNode.next = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { - commandNode.next = stack[topOf(state).index]; + _nextCommandNode = stack[topOf(state).index]; } else { - commandNode.next = nextCommandNode; + _nextCommandNode = nextCommandNode; } + connectNext(commandNode, _nextCommandNode); }, do: function (commandNode, nextCommandNode, stack, state) { state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - commandNode.next = nextCommandNode; + connectNext(commandNode, nextCommandNode); }, else: function (commandNode, nextCommandNode) { - commandNode.next = nextCommandNode; + connectNext(commandNode, nextCommandNode); }, elseIf: function (commandNode, nextCommandNode, stack) { - commandNode.right = nextCommandNode; - commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + connectConditional(commandNode, nextCommandNode, stack); }, end: function (commandNode, nextCommandNode, stack, state) { state.pop(); if (!isEmpty(state)) { + let _nextCommandNode; if (isElseOrElseIf(nextCommandNode.command)) { - commandNode.next = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); } else { - commandNode.next = nextCommandNode; + _nextCommandNode = nextCommandNode; } + connectNext(commandNode, _nextCommandNode); } }, if: function (commandNode, nextCommandNode, stack, state) { state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - commandNode.right = nextCommandNode; - commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + connectConditional(commandNode, nextCommandNode, stack, state); }, repeatIf: function (commandNode, nextCommandNode, stack, state) { - commandNode.right = stack[topOf(state).index]; - commandNode.left = nextCommandNode; + connectRepeatIf(commandNode, nextCommandNode, stack, state); state.pop(); }, times: function (commandNode, nextCommandNode, stack, state) { state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - commandNode.right = nextCommandNode; - commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + connectConditional(commandNode, nextCommandNode, stack); }, while: function (commandNode, nextCommandNode, stack, state) { state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - commandNode.right = nextCommandNode; - commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + connectConditional(commandNode, nextCommandNode, stack); } }; +function connectConditional (commandNode, nextCommandNode, stack) { + commandNode.right = nextCommandNode; + commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); +} + +function connectNext (commandNode, nextCommandNode) { + commandNode.next = nextCommandNode; +} + +function connectRepeatIf (commandNode, nextCommandNode, stack, state) { + commandNode.right = stack[topOf(state).index]; + commandNode.left = nextCommandNode; +} + function findNextNodeBy(stack, index, level, commandName) { for(let i = index + 1; i < stack.length + 1; i++) { if (commandName) { From ca82fe67f26723ff5be1aca2f087eae6719f06c7 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 10 Jul 2018 08:37:09 -0400 Subject: [PATCH 035/125] Reduced syntax verification, leveling, and node connecting further. Added a test for the final tree object that gets created. Also started to suss out execution and reference updating in CommandNode. --- .../__test__/playback/playback-tree.spec.js | 61 +++-- .../playback/playback-tree/command-node.js | 20 ++ .../src/neo/playback/playback-tree/index.js | 228 ++++++++---------- 3 files changed, 167 insertions(+), 142 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 793ac9f95..189b37213 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { deriveCommandLevels, verifyControlFlowSyntax, createPlaybackTree } from "../../playback/playback-tree"; +import { createPlaybackTree, deriveCommandLevels, verifyControlFlowSyntax, createCommandNodesFromCommandStack } from "../../playback/playback-tree"; import Command from "../../models/Command"; describe("Control Flow", () => { @@ -203,18 +203,18 @@ describe("Control Flow", () => { describe("Linked List Validation", () => { test("nodes contain command references and levels", () => { let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; - let automata = createPlaybackTree(input); - expect(automata[0].command).toEqual(input[0]); - expect(automata[0].level).toEqual(0); - expect(automata[1].command).toEqual(input[1]); - expect(automata[1].level).toEqual(0); + let stack = createCommandNodesFromCommandStack(input); + expect(stack[0].command).toEqual(input[0]); + expect(stack[0].level).toEqual(0); + expect(stack[1].command).toEqual(input[1]); + expect(stack[1].level).toEqual(0); }); test("command-command", () => { let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].left).toBeUndefined(); expect(stack[0].right).toBeUndefined(); @@ -232,7 +232,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -268,7 +268,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); @@ -286,7 +286,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[3]); @@ -309,7 +309,7 @@ describe("Control Flow", () => { new Command(null, "end", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -346,7 +346,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); @@ -387,7 +387,7 @@ describe("Control Flow", () => { new Command(null, "repeatIf", "", ""), new Command(null, "command", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); @@ -410,7 +410,7 @@ describe("Control Flow", () => { new Command(null, "end", "", ""), new Command(null, "repeatIf", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); @@ -436,7 +436,7 @@ describe("Control Flow", () => { new Command(null, "command", "", ""), new Command(null, "end", "", "") ]; - let stack = createPlaybackTree(input); + let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); @@ -449,4 +449,35 @@ describe("Control Flow", () => { }); }); }); + describe("Processed", () => { + it("populated tree exists with correct values", () => { + let input = [ + new Command(null, "if", "", ""), + new Command(null, "command", "", ""), + new Command(null, "else", "", ""), + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "do", "", ""), + new Command(null, "command", "", ""), + new Command(null, "while", "", ""), + new Command(null, "command", "", ""), + new Command(null, "end", "", ""), + new Command(null, "repeatIf", "", ""), + new Command(null, "end", "", "") + ]; + let tree = createPlaybackTree(input); + expect(tree.currentCommandNode.command).toEqual(input[0]); // if + expect(tree.currentCommandNode.right.command).toEqual(input[1]); // if -> command + expect(tree.currentCommandNode.right.next.command).toEqual(input[12]); // if command -> end + expect(tree.currentCommandNode.left.command).toEqual(input[2]); // if -> else + expect(tree.currentCommandNode.left.next.right.command).toEqual(input[4]); // while -> command + expect(tree.currentCommandNode.left.next.left.command).toEqual(input[5]); // while -> end + expect(tree.currentCommandNode.left.next.left.next.next.command).toEqual(input[7]); // do -> command + expect(tree.currentCommandNode.left.next.left.next.next.next.right.command).toEqual(input[9]); // while -> command + expect(tree.currentCommandNode.left.next.left.next.next.next.left.command).toEqual(input[10]); // while -> end + expect(tree.currentCommandNode.left.next.left.next.next.next.left.next.right.command).toEqual(input[6]); // repeatIf -> do + expect(tree.currentCommandNode.left.next.left.next.next.next.left.next.left.command).toEqual(input[12]); // repeatIf -> end + }); + }); }); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 300ed611f..f0d97c2dd 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -25,4 +25,24 @@ export class CommandNode { this.level; this.timesVisited = 0; } + + isConditional() { + !!(this.left && this.right); + } + + evaluate(playbackTree, extCommand) { + if (this.isConditional()) { + if (extCommand.evaluateConditional(this.command.target)) { + playbackTree.currentCommandNode = this.right; + } else { + playbackTree.currentCommandNode = this.left; + } + } else { + if (this.command.command === "end") { + playbackTree.currentCommandNode = this.next; + } else { + extCommand.sendMessage(this.command, this.command.target, this.command.value, false); + } + } + } } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 9cf3aad9b..ff96f55ed 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -16,22 +16,29 @@ // under the License. import { CommandNode } from "./command-node"; -export { createPlaybackTree, verifyControlFlowSyntax, deriveCommandLevels, connectCommandNodes }; +export { createPlaybackTree }; // public API +export { createCommandNodesFromCommandStack, verifyControlFlowSyntax, deriveCommandLevels }; // for testing function createPlaybackTree(commandStack) { + let nodes = createCommandNodesFromCommandStack(commandStack); + return PlaybackTree.create(nodes); +} + +function createCommandNodesFromCommandStack(commandStack) { verifyControlFlowSyntax(commandStack); let levels = deriveCommandLevels(commandStack); let initNodes = initCommandNodes(commandStack, levels); return connectCommandNodes(initNodes); - //Automata.startingCommandNode = nodes[0]; - //Automata.currentCommandNode = nodes[0]; - //return Automata; } -class Automata { - constructor() { - this.startingCommandNode; - this.currentCommandNode; +class PlaybackTree { + constructor(initialCommandNode) { + this.startingCommandNode = initialCommandNode; // to prevent garbage collection of tree + this.currentCommandNode = initialCommandNode; + } + + static create(commandNodeStack) { + return new PlaybackTree(commandNodeStack[0]); } } @@ -104,30 +111,14 @@ function verifyControlFlowSyntax(commandStack) { } const verifyCommand = { - do: function (commandName, commandIndex, stack, state) { - trackControlFlowBranchOpen(commandName, commandIndex, stack, state); - }, - else: function (commandName, commandIndex, stack, state) { - verifyElse(commandName, commandIndex, stack, state); - }, - elseIf: function (commandName, commandIndex, stack, state) { - verifyElse(commandName, commandIndex, stack, state); - }, - end: function (commandName, commandIndex, stack, state) { - verifyEnd(commandName, commandIndex, stack, state); - }, - if: function (commandName, commandIndex, stack, state) { - trackControlFlowBranchOpen(commandName, commandIndex, stack, state); - }, - repeatIf: function (commandName, commandIndex, stack, state) { - verifyRepeatIf(commandName, commandIndex, stack, state); - }, - times: function (commandName, commandIndex, stack, state) { - trackControlFlowBranchOpen(commandName, commandIndex, stack, state); - }, - while: function (commandName, commandIndex, stack, state) { - trackControlFlowBranchOpen(commandName, commandIndex, stack, state); - } + do: trackControlFlowBranchOpen, + else: verifyElse, + elseIf: verifyElse, + end: verifyEnd, + if: trackControlFlowBranchOpen, + repeatIf: verifyRepeatIf, + times: trackControlFlowBranchOpen, + while: trackControlFlowBranchOpen }; function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { @@ -180,54 +171,41 @@ function deriveCommandLevels(commandStack) { } let levelCommand = { - default: function (command, level, levels) { - levels.push(level); - return level; - }, - do: function (command, level, levels) { - levels.push(level); - level++; - return level; - }, - else: function (command, level, levels) { - level--; - levels.push(level); - level++; - return level; - }, - elseIf: function (command, level, levels) { - level--; - levels.push(level); - level++; - return level; - }, - end: function (command, level, levels) { - level--; - levels.push(level); - return level; - }, - if: function (command, level, levels) { - levels.push(level); - level++; - return level; - }, - repeatIf: function (command, level, levels) { - level--; - levels.push(level); - return level; - }, - times: function (command, level, levels) { - levels.push(level); - level++; - return level; - }, - while: function (command, level, levels) { - levels.push(level); - level++; - return level; - } + default: levelDefault, + do: levelBranchOpen, + else: levelElse, + elseIf: levelElse, + end: levelBranchEnd, + if: levelBranchOpen, + repeatIf: levelBranchEnd, + times: levelBranchOpen, + while: levelBranchOpen }; +function levelDefault (command, level, levels) { + levels.push(level); + return level; +} + +function levelBranchOpen (command, level, levels) { + levels.push(level); + level++; + return level; +} + +function levelBranchEnd (command, level, levels) { + level--; + levels.push(level); + return level; +} + +function levelElse (command, level, levels) { + level--; + levels.push(level); + level++; + return level; +} + function initCommandNodes(commandStack, levels) { let commandNodes = []; commandStack.forEach(function(command, index) { @@ -256,62 +234,44 @@ function connectCommandNodes(commandNodeStack) { } let connectCommandNode = { - default: function (commandNode, nextCommandNode, stack, state) { - let _nextCommandNode; - if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); - } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { - _nextCommandNode = stack[topOf(state).index]; - } else { - _nextCommandNode = nextCommandNode; - } - connectNext(commandNode, _nextCommandNode); - }, - do: function (commandNode, nextCommandNode, stack, state) { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - connectNext(commandNode, nextCommandNode); - }, - else: function (commandNode, nextCommandNode) { - connectNext(commandNode, nextCommandNode); - }, - elseIf: function (commandNode, nextCommandNode, stack) { - connectConditional(commandNode, nextCommandNode, stack); - }, - end: function (commandNode, nextCommandNode, stack, state) { - state.pop(); - if (!isEmpty(state)) { - let _nextCommandNode; - if (isElseOrElseIf(nextCommandNode.command)) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); - } else { - _nextCommandNode = nextCommandNode; - } - connectNext(commandNode, _nextCommandNode); - } - }, - if: function (commandNode, nextCommandNode, stack, state) { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - connectConditional(commandNode, nextCommandNode, stack, state); - }, - repeatIf: function (commandNode, nextCommandNode, stack, state) { - connectRepeatIf(commandNode, nextCommandNode, stack, state); - state.pop(); - }, - times: function (commandNode, nextCommandNode, stack, state) { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - connectConditional(commandNode, nextCommandNode, stack); - }, - while: function (commandNode, nextCommandNode, stack, state) { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - connectConditional(commandNode, nextCommandNode, stack); - } + default: connectDefault, + do: connectNextForBranchOpen, + else: connectNext, + elseIf: connectConditional, + end: connectEnd, + if: connectConditionalForBranchOpen, + repeatIf: connectRepeatIf, + times: connectConditionalForBranchOpen, + while: connectConditionalForBranchOpen }; +function connectDefault (commandNode, nextCommandNode, stack, state) { + let _nextCommandNode; + if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { + _nextCommandNode = stack[topOf(state).index]; + } else { + _nextCommandNode = nextCommandNode; + } + connectNext(commandNode, _nextCommandNode); +} + +function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + connectConditional(commandNode, nextCommandNode, stack); +} + function connectConditional (commandNode, nextCommandNode, stack) { commandNode.right = nextCommandNode; commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); } +function connectNextForBranchOpen (commandNode, nextCommandNode, stack, state) { + state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); + connectNext(commandNode, nextCommandNode); +} + function connectNext (commandNode, nextCommandNode) { commandNode.next = nextCommandNode; } @@ -319,6 +279,20 @@ function connectNext (commandNode, nextCommandNode) { function connectRepeatIf (commandNode, nextCommandNode, stack, state) { commandNode.right = stack[topOf(state).index]; commandNode.left = nextCommandNode; + state.pop(); +} + +function connectEnd (commandNode, nextCommandNode, stack, state) { + state.pop(); + if (!isEmpty(state)) { + let _nextCommandNode; + if (isElseOrElseIf(nextCommandNode.command)) { + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + } else { + _nextCommandNode = nextCommandNode; + } + connectNext(commandNode, _nextCommandNode); + } } function findNextNodeBy(stack, index, level, commandName) { From 2b19664898b1c6fe228b4b926ad431bb8729a8a8 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 10 Jul 2018 13:30:10 -0400 Subject: [PATCH 036/125] Added commands to the CommandList along with their names, descriptions, and ArgTypes. Updated CommandName to use the CommandList ids, renamed it to ControlFlowCommandNames, and moved it into Command for reuse. Updated playback-tree to use it. --- .../__test__/playback/playback-tree.spec.js | 268 +++++++++--------- .../selenium-ide/src/neo/models/Command.js | 63 ++++ .../src/neo/playback/playback-tree/index.js | 94 +++--- packages/selianize/src/command.js | 5 +- 4 files changed, 244 insertions(+), 186 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 189b37213..7375e9239 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -16,26 +16,30 @@ // under the License. import { createPlaybackTree, deriveCommandLevels, verifyControlFlowSyntax, createCommandNodesFromCommandStack } from "../../playback/playback-tree"; -import Command from "../../models/Command"; +import Command, { ControlFlowCommandNames } from "../../models/Command"; + +function createCommand(name) { + return new Command(null, name, "", ""); +} describe("Control Flow", () => { describe("Preprocess", () => { describe("Leveling", () => { test("returns leveled command stack", () => { let stack = deriveCommandLevels([ - new Command(null, "if", "", ""), - new Command(null, "command", "", ""), - new Command(null, "else", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "do", "", ""), - new Command(null, "command", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "repeatIf", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("command"), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.do), + createCommand("command"), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf), + createCommand(ControlFlowCommandNames.end) ]); expect(stack[0]).toEqual(0); // if expect(stack[1]).toEqual(1); // command @@ -55,146 +59,146 @@ describe("Control Flow", () => { describe("Syntax Validation", () => { test("if, end", () => { let result = verifyControlFlowSyntax([ - new Command(null, "if", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("if, else, end", () => { let result = verifyControlFlowSyntax([ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("if, elseIf, end", () => { let result = verifyControlFlowSyntax([ - new Command(null, "if", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("if, elseIf, else, end", () => { let result = verifyControlFlowSyntax([ - new Command(null, "if", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "else", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("while, end", () => { let result = new verifyControlFlowSyntax([ - new Command(null, "while", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("times, end", () => { let result = verifyControlFlowSyntax([ - new Command(null, "times", "", ""), - new Command(null, "end", "", "") + createCommand("times"), + createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); }); test("do, repeatIf", () => { let result = verifyControlFlowSyntax([ - new Command(null, "do", "", ""), - new Command(null, "repeatIf", "", "") + createCommand(ControlFlowCommandNames.do), + createCommand(ControlFlowCommandNames.repeatIf) ]); expect(result).toBeTruthy(); }); test("do, while, end, repeatIf", () => { let result = verifyControlFlowSyntax([ - new Command(null, "do", "", ""), - new Command(null, "while", "", ""), - new Command(null, "end", "", ""), - new Command(null, "repeatIf", "", "") + createCommand(ControlFlowCommandNames.do), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf) ]); expect(result).toBeTruthy(); }); }); describe("Syntax Invalidation", () => { test("if", () => { - let input = [new Command(null, "if", "", "")]; + let input = [createCommand(ControlFlowCommandNames.if)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, if, end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "if", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.end) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, else, elseIf, end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.end) ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incorrect command order of elseIf / else"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incorrect command order of else if / else"); }); test("if, else, else, end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "else", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Too many else commands used"); }); - test("while", () => { - let input = [new Command(null, "while", "", "")]; + test(ControlFlowCommandNames.while, () => { + let input = [createCommand(ControlFlowCommandNames.while)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); }); test("if, while", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "while", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.while) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); }); test("if, while, end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "while", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); }); test("if, while, else, end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "else", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "while", "", ""), - new Command(null, "else", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand("elseIf"), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else / elseIf used outside of an if block"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else / else if used outside of an if block"); }); test("times", () => { - let input = [new Command(null, "times", "", "")]; + let input = [createCommand("times")]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at times"); }); - test("repeatIf", () => { - let input = [new Command(null, "repeatIf", "", "")]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("A repeatIf used without a do block"); + test(ControlFlowCommandNames.repeatIf, () => { + let input = [createCommand(ControlFlowCommandNames.repeatIf)]; + expect(function() { verifyControlFlowSyntax(input); }).toThrow("A repeat if used without a do block"); }); - test("do", () => { - let input = [new Command(null, "do", "", "")]; + test(ControlFlowCommandNames.do, () => { + let input = [createCommand(ControlFlowCommandNames.do)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at do"); }); - test("end", () => { - let input = [new Command(null, "end", "", "")]; + test(ControlFlowCommandNames.end, () => { + let input = [createCommand(ControlFlowCommandNames.end)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Use of end without an opening keyword"); }); }); @@ -202,7 +206,7 @@ describe("Control Flow", () => { describe("Process", () => { describe("Linked List Validation", () => { test("nodes contain command references and levels", () => { - let input = [ new Command(null, "command1", "", ""), new Command(null, "command2", "", "") ]; + let input = [ createCommand("command1"), createCommand("command2") ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].command).toEqual(input[0]); expect(stack[0].level).toEqual(0); @@ -211,8 +215,8 @@ describe("Control Flow", () => { }); test("command-command", () => { let input = [ - new Command(null, "command1", "", ""), - new Command(null, "command2", "", "") + createCommand("command1"), + createCommand("command2") ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); @@ -224,13 +228,13 @@ describe("Control Flow", () => { }); test("if-command-elseIf-command-else-command-end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "command", "", ""), - new Command(null, "elseIf", "", ""), - new Command(null, "command", "", ""), - new Command(null, "else", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("command"), + createCommand("elseIf"), + createCommand("command"), + createCommand(ControlFlowCommandNames.else), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); // if @@ -264,9 +268,9 @@ describe("Control Flow", () => { }); test("while-command-end", () => { let input = [ - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); @@ -281,10 +285,10 @@ describe("Control Flow", () => { }); test("while-command-command-end", () => { let input = [ - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); @@ -302,12 +306,12 @@ describe("Control Flow", () => { }); test("if-command-while-command-end-end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "command", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("command"), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); // if @@ -337,14 +341,14 @@ describe("Control Flow", () => { }); test("if-while-command-end-command-else-command-end", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "command", "", ""), - new Command(null, "else", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand("command"), + createCommand(ControlFlowCommandNames.else), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); // if @@ -382,10 +386,10 @@ describe("Control Flow", () => { }); test("do-command-repeatIf-end", () => { let input = [ - new Command(null, "do", "", ""), - new Command(null, "command", "", ""), - new Command(null, "repeatIf", "", ""), - new Command(null, "command", "", "") + createCommand(ControlFlowCommandNames.do), + createCommand("command"), + createCommand(ControlFlowCommandNames.repeatIf), + createCommand("command") ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); @@ -403,12 +407,12 @@ describe("Control Flow", () => { }); test("do-command-while-end-repeatIf", () => { let input = [ - new Command(null, "do", "", ""), - new Command(null, "command", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "repeatIf", "", "") + createCommand(ControlFlowCommandNames.do), + createCommand("command"), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf) ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toEqual(stack[1]); @@ -432,9 +436,9 @@ describe("Control Flow", () => { }); test("times-command-end", () => { let input = [ - new Command(null, "times", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", "") + createCommand("times"), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); @@ -452,19 +456,19 @@ describe("Control Flow", () => { describe("Processed", () => { it("populated tree exists with correct values", () => { let input = [ - new Command(null, "if", "", ""), - new Command(null, "command", "", ""), - new Command(null, "else", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "do", "", ""), - new Command(null, "command", "", ""), - new Command(null, "while", "", ""), - new Command(null, "command", "", ""), - new Command(null, "end", "", ""), - new Command(null, "repeatIf", "", ""), - new Command(null, "end", "", "") + createCommand(ControlFlowCommandNames.if), + createCommand("command"), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.do), + createCommand("command"), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf), + createCommand(ControlFlowCommandNames.end) ]; let tree = createPlaybackTree(input); expect(tree.currentCommandNode.command).toEqual(input[0]); // if diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 301d3cd3d..5028b1991 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -111,6 +111,10 @@ export const ArgTypes = { name: "attribute locator", description: "An element locator followed by an @ sign and then the name of the attribute, e.g. \"foo@bar\"." }, + conditionalExpression: { + name: "conditional expression", + description: "JavaScript expression that returns a boolean result for use in control flow commands." + }, coord: { name: "coord String", description: "Specifies the x,y position (e.g., - 10,20) of the mouse event \ @@ -173,6 +177,10 @@ export const ArgTypes = { name: "text", description: "The text to verify." }, + times: { + name: "times", + description: "The number of attempts a times control flow loop will execute the commands within its block." + }, url: { name: "url", description: "The URL to open (may be relative or absolute)." @@ -357,6 +365,50 @@ class CommandList { description: "Check a toggle-button (checkbox/radio).", target: ArgTypes.locator }], + [ "controlFlowDo", { + name: "do", + type: TargetTypes.LOCATOR, + description: "Create a loop that executes the proceeding commands at least once. Terminate the branch with the repeat if command." + }], + [ "controlFlowElse", { + name: "else", + type: TargetTypes.LOCATOR, + description: "Part of an if block. Execute the commands in this branch when an if and/or else if condition are not met. Terminate the branch with the end command." + }], + [ "controlFlowElseIf", { + name: "elseIf", + type: TargetTypes.LOCATOR, + description: "Part of an if block. Execute the commands in this branch when an if condition has not been met. Terminate the branch with the end command." + }], + [ "controlFlowEnd", { + name: "end", + type: TargetTypes.LOCATOR, + description: "Terminates a control flow block for if, while, and times." + }], + [ "controlFlowIf", { + name: "if", + type: TargetTypes.LOCATOR, + description: "Create a conditional branch in your test. Terminate the branch with the end command.", + target: ArgTypes.conditionalExpression + }], + [ "controlFlowRepeatIf", { + name: "repeatIf", + type: TargetTypes.LOCATOR, + description: "Terminate a do control flow branch conditionally. If the result of the provided conditional expression is true, it starts the do loop over. Otherwise it ends the loop.", + target: ArgTypes.conditionalExpression + }], + [ "controlFlowTimes", { + name: "times", + type: TargetTypes.LOCATOR, + description: "Create a loop that executes the proceeding commands n number of times.", + target: ArgTypes.times + }], + [ "controlFlowWhile", { + name: "while", + type: TargetTypes.LOCATOR, + description: "Create a loop that executes the proceeding commands repeatedly for as long as the provided conditional expression is true.", + target: ArgTypes.conditionalExpression + }], [ "uncheck", { name: "uncheck", type: TargetTypes.LOCATOR, @@ -738,3 +790,14 @@ class CommandList { } export const Commands = CommandList.instance; + +export const ControlFlowCommandNames = { + do: Commands.list.get("controlFlowDo").name, + else: Commands.list.get("controlFlowElse").name, + elseIf: Commands.list.get("controlFlowElseIf").name, + end: Commands.list.get("controlFlowEnd").name, + if: Commands.list.get("controlFlowIf").name, + repeatIf: Commands.list.get("controlFlowRepeatIf").name, + times: Commands.list.get("controlFlowTimes").name, + while: Commands.list.get("controlFlowWhile").name +}; diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index ff96f55ed..ff9e70bc5 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -16,6 +16,7 @@ // under the License. import { CommandNode } from "./command-node"; +import { ControlFlowCommandNames } from "../../models/Command"; export { createPlaybackTree }; // public API export { createCommandNodesFromCommandStack, verifyControlFlowSyntax, deriveCommandLevels }; // for testing @@ -42,41 +43,30 @@ class PlaybackTree { } } -const CommandName = { - do: "do", - else: "else", - elseIf: "elseIf", - end: "end", - if: "if", - repeatIf: "repeatIf", - times: "times", - while: "while" -}; - function isDo(command) { - return (command.command === CommandName.do); + return (command.command === ControlFlowCommandNames.do); } function isElse(command) { - return (command.command === CommandName.else); + return (command.command === ControlFlowCommandNames.else); } function isElseOrElseIf(command) { - return (command.command === CommandName.else || - command.command === CommandName.elseIf); + return (command.command === ControlFlowCommandNames.else || + command.command === ControlFlowCommandNames.elseIf); } function isEnd(command) { - return (command.command === CommandName.end); + return (command.command === ControlFlowCommandNames.end); } function isIf(command) { - return (command.command === CommandName.if); + return (command.command === ControlFlowCommandNames.if); } function isLoop(command) { - return (command.command === CommandName.while || - command.command === CommandName.times); + return (command.command === ControlFlowCommandNames.while || + command.command === ControlFlowCommandNames.times); } function isEmpty(obj) { @@ -111,14 +101,14 @@ function verifyControlFlowSyntax(commandStack) { } const verifyCommand = { - do: trackControlFlowBranchOpen, - else: verifyElse, - elseIf: verifyElse, - end: verifyEnd, - if: trackControlFlowBranchOpen, - repeatIf: verifyRepeatIf, - times: trackControlFlowBranchOpen, - while: trackControlFlowBranchOpen + [ControlFlowCommandNames.do]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.else]: verifyElse, + [ControlFlowCommandNames.elseIf]: verifyElse, + [ControlFlowCommandNames.end]: verifyEnd, + [ControlFlowCommandNames.if]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.repeatIf]: verifyRepeatIf, + [ControlFlowCommandNames.times]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.while]: trackControlFlowBranchOpen }; function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { @@ -127,7 +117,7 @@ function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { function verifyElse (commandName, commandIndex, stack, state) { if (!isIf(topOf(state))) { - throw "An else / elseIf used outside of an if block"; + throw "An else / else if used outside of an if block"; } } @@ -137,11 +127,11 @@ function verifyEnd (commandName, commandIndex, stack, state) { } else if (isIf(topOf(state))) { const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; const allElses = stack.slice(topOf(state).index, commandIndex).filter( - command => (command.command === CommandName.else || command.command === CommandName.elseIf)); + command => (command.command === ControlFlowCommandNames.else || command.command === ControlFlowCommandNames.elseIf)); if (numberOfElse > 1) { throw "Too many else commands used"; } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { - throw "Incorrect command order of elseIf / else"; + throw "Incorrect command order of else if / else"; } else if (numberOfElse === 0 || isElse(topOf(allElses))) { state.pop(); } @@ -152,7 +142,7 @@ function verifyEnd (commandName, commandIndex, stack, state) { function verifyRepeatIf (commandName, commandIndex, stack, state) { if (!isDo(topOf(state))) { - throw "A repeatIf used without a do block"; + throw "A repeat if used without a do block"; } state.pop(); } @@ -164,22 +154,21 @@ function deriveCommandLevels(commandStack) { if (levelCommand[command.command]) { level = levelCommand[command.command](command, level, levels); } else { - levelCommand["default"](command, level, levels); + levelDefault(command, level, levels); } }); return levels; } let levelCommand = { - default: levelDefault, - do: levelBranchOpen, - else: levelElse, - elseIf: levelElse, - end: levelBranchEnd, - if: levelBranchOpen, - repeatIf: levelBranchEnd, - times: levelBranchOpen, - while: levelBranchOpen + [ControlFlowCommandNames.do]: levelBranchOpen, + [ControlFlowCommandNames.else]: levelElse, + [ControlFlowCommandNames.elseIf]: levelElse, + [ControlFlowCommandNames.end]: levelBranchEnd, + [ControlFlowCommandNames.if]: levelBranchOpen, + [ControlFlowCommandNames.repeatIf]: levelBranchEnd, + [ControlFlowCommandNames.times]: levelBranchOpen, + [ControlFlowCommandNames.while]: levelBranchOpen }; function levelDefault (command, level, levels) { @@ -226,7 +215,7 @@ function connectCommandNodes(commandNodeStack) { if (connectCommandNode[commandNode.command.command]) { connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, _commandNodeStack, state); } else { - connectCommandNode["default"](commandNode, nextCommandNode, _commandNodeStack, state); + connectDefault(commandNode, nextCommandNode, _commandNodeStack, state); } } }); @@ -234,21 +223,20 @@ function connectCommandNodes(commandNodeStack) { } let connectCommandNode = { - default: connectDefault, - do: connectNextForBranchOpen, - else: connectNext, - elseIf: connectConditional, - end: connectEnd, - if: connectConditionalForBranchOpen, - repeatIf: connectRepeatIf, - times: connectConditionalForBranchOpen, - while: connectConditionalForBranchOpen + [ControlFlowCommandNames.do]: connectNextForBranchOpen, + [ControlFlowCommandNames.else]: connectNext, + [ControlFlowCommandNames.elseIf]: connectConditional, + [ControlFlowCommandNames.end]: connectEnd, + [ControlFlowCommandNames.if]: connectConditionalForBranchOpen, + [ControlFlowCommandNames.repeatIf]: connectRepeatIf, + [ControlFlowCommandNames.times]: connectConditionalForBranchOpen, + [ControlFlowCommandNames.while]: connectConditionalForBranchOpen }; function connectDefault (commandNode, nextCommandNode, stack, state) { let _nextCommandNode; if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, ControlFlowCommandNames.end); } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { _nextCommandNode = stack[topOf(state).index]; } else { @@ -287,7 +275,7 @@ function connectEnd (commandNode, nextCommandNode, stack, state) { if (!isEmpty(state)) { let _nextCommandNode; if (isElseOrElseIf(nextCommandNode.command)) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, CommandName.end); + _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, ControlFlowCommandNames.end); } else { _nextCommandNode = nextCommandNode; } diff --git a/packages/selianize/src/command.js b/packages/selianize/src/command.js index 63598e50c..8ef1c8e4b 100644 --- a/packages/selianize/src/command.js +++ b/packages/selianize/src/command.js @@ -93,7 +93,8 @@ const emitters = { chooseCancelOnNextConfirmation: skip, chooseCancelOnNextPrompt: skip, chooseOkOnNextConfirmation: skip, - setSpeed: skip + setSpeed: skip, + controlFlowIf: emitControlFlowIf }; export function emit(command) { @@ -337,3 +338,5 @@ async function emitSubmit(locator) { function skip() { return Promise.resolve(); } + +function emitControlFlowIf() { } From 389f9a8b54ddad0983628dec987343b8324e0216 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 10 Jul 2018 13:50:11 -0400 Subject: [PATCH 037/125] Added no-op emitters for control flow commands to suffice test errors. --- packages/selianize/src/command.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/selianize/src/command.js b/packages/selianize/src/command.js index 8ef1c8e4b..6eed6f230 100644 --- a/packages/selianize/src/command.js +++ b/packages/selianize/src/command.js @@ -94,7 +94,14 @@ const emitters = { chooseCancelOnNextPrompt: skip, chooseOkOnNextConfirmation: skip, setSpeed: skip, - controlFlowIf: emitControlFlowIf + controlFlowDo: emitControlFlow, + controlFlowElse: emitControlFlow, + controlFlowElseIf: emitControlFlow, + controlFlowEnd: emitControlFlow, + controlFlowIf: emitControlFlow, + controlFlowRepeatIf: emitControlFlow, + controlFlowTimes: emitControlFlow, + controlFlowWhile: emitControlFlow }; export function emit(command) { @@ -339,4 +346,4 @@ function skip() { return Promise.resolve(); } -function emitControlFlowIf() { } +function emitControlFlow() { } From 1fcaf021cde9ac2695a13e26b9edb3741749ea0f Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 10 Jul 2018 13:51:49 -0400 Subject: [PATCH 038/125] Moved PlaybackTree to its own file. --- .../src/neo/playback/playback-tree/index.js | 12 +------- .../playback/playback-tree/playback-tree.js | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/playback-tree.js diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index ff9e70bc5..f2950f386 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -16,6 +16,7 @@ // under the License. import { CommandNode } from "./command-node"; +import { PlaybackTree } from "./playback-tree"; import { ControlFlowCommandNames } from "../../models/Command"; export { createPlaybackTree }; // public API export { createCommandNodesFromCommandStack, verifyControlFlowSyntax, deriveCommandLevels }; // for testing @@ -32,17 +33,6 @@ function createCommandNodesFromCommandStack(commandStack) { return connectCommandNodes(initNodes); } -class PlaybackTree { - constructor(initialCommandNode) { - this.startingCommandNode = initialCommandNode; // to prevent garbage collection of tree - this.currentCommandNode = initialCommandNode; - } - - static create(commandNodeStack) { - return new PlaybackTree(commandNodeStack[0]); - } -} - function isDo(command) { return (command.command === ControlFlowCommandNames.do); } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/playback-tree.js b/packages/selenium-ide/src/neo/playback/playback-tree/playback-tree.js new file mode 100644 index 000000000..a0f3e4992 --- /dev/null +++ b/packages/selenium-ide/src/neo/playback/playback-tree/playback-tree.js @@ -0,0 +1,28 @@ +// 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 class PlaybackTree { + constructor(initialCommandNode) { + this.startingCommandNode = initialCommandNode; // to prevent garbage collection of tree + this.currentCommandNode = initialCommandNode; + } + + static create(commandNodeStack) { + return new PlaybackTree(commandNodeStack[0]); + } +} + From 8871df9d02e384308dc68ac6ce4402a309f6ea9a Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 10 Jul 2018 14:40:19 -0400 Subject: [PATCH 039/125] Dropped the 'ControlFlow' prefix from the command names and alphabetized them with the rest of the commands. --- .../src/neo/IO/SideeX/playback.js | 3 + .../selenium-ide/src/neo/models/Command.js | 108 +++++++++--------- .../playback/playback-tree/command-node.js | 4 +- packages/selianize/src/command.js | 16 +-- 4 files changed, 68 insertions(+), 63 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 016589346..3936db9d3 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -23,6 +23,7 @@ import UiState from "../../stores/view/UiState"; import { canExecuteCommand, executeCommand } from "../../../plugin/commandExecutor"; import ExtCommand, { isExtCommand } from "./ext-command"; import { xlateArgument } from "./formatCommand"; +//import { createPlaybackTree } from "../../playback/playback-tree"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -37,10 +38,12 @@ extCommand.doSetSpeed = (speed) => { let baseUrl = ""; let ignoreBreakpoint = false; +//let playbackTree; function play(currUrl) { baseUrl = currUrl; ignoreBreakpoint = false; + //playbackTree = createPlaybackTree(PlaybackState.runningQueue); prepareToPlay() .then(executionLoop) .then(finishPlaying) diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 5028b1991..4ba91cdea 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -365,56 +365,11 @@ class CommandList { description: "Check a toggle-button (checkbox/radio).", target: ArgTypes.locator }], - [ "controlFlowDo", { + [ "do", { name: "do", type: TargetTypes.LOCATOR, description: "Create a loop that executes the proceeding commands at least once. Terminate the branch with the repeat if command." }], - [ "controlFlowElse", { - name: "else", - type: TargetTypes.LOCATOR, - description: "Part of an if block. Execute the commands in this branch when an if and/or else if condition are not met. Terminate the branch with the end command." - }], - [ "controlFlowElseIf", { - name: "elseIf", - type: TargetTypes.LOCATOR, - description: "Part of an if block. Execute the commands in this branch when an if condition has not been met. Terminate the branch with the end command." - }], - [ "controlFlowEnd", { - name: "end", - type: TargetTypes.LOCATOR, - description: "Terminates a control flow block for if, while, and times." - }], - [ "controlFlowIf", { - name: "if", - type: TargetTypes.LOCATOR, - description: "Create a conditional branch in your test. Terminate the branch with the end command.", - target: ArgTypes.conditionalExpression - }], - [ "controlFlowRepeatIf", { - name: "repeatIf", - type: TargetTypes.LOCATOR, - description: "Terminate a do control flow branch conditionally. If the result of the provided conditional expression is true, it starts the do loop over. Otherwise it ends the loop.", - target: ArgTypes.conditionalExpression - }], - [ "controlFlowTimes", { - name: "times", - type: TargetTypes.LOCATOR, - description: "Create a loop that executes the proceeding commands n number of times.", - target: ArgTypes.times - }], - [ "controlFlowWhile", { - name: "while", - type: TargetTypes.LOCATOR, - description: "Create a loop that executes the proceeding commands repeatedly for as long as the provided conditional expression is true.", - target: ArgTypes.conditionalExpression - }], - [ "uncheck", { - name: "uncheck", - type: TargetTypes.LOCATOR, - description: "Uncheck a toggle-button (checkbox/radio).", - target: ArgTypes.locator - }], [ "doubleClick", { name: "double click", type: TargetTypes.LOCATOR, @@ -445,6 +400,21 @@ class CommandList { Useful for debugging.", target: ArgTypes.message }], + [ "else", { + name: "else", + type: TargetTypes.LOCATOR, + description: "Part of an if block. Execute the commands in this branch when an if and/or else if condition are not met. Terminate the branch with the end command." + }], + [ "elseIf", { + name: "elseIf", + type: TargetTypes.LOCATOR, + description: "Part of an if block. Execute the commands in this branch when an if condition has not been met. Terminate the branch with the end command." + }], + [ "end", { + name: "end", + type: TargetTypes.LOCATOR, + description: "Terminates a control flow block for if, while, and times." + }], [ "executeScript", { name: "execute script", description: "Executes a snippet of JavaScript in the context of the currently selected frame or \ @@ -466,6 +436,12 @@ class CommandList { target: ArgTypes.locator, value: ArgTypes.value }], + [ "if", { + name: "if", + type: TargetTypes.LOCATOR, + description: "Create a conditional branch in your test. Terminate the branch with the end command.", + target: ArgTypes.conditionalExpression + }], [ "mouseDownAt", { name: "mouse down at", type: TargetTypes.LOCATOR, @@ -521,6 +497,12 @@ class CommandList { target: ArgTypes.locator, value: ArgTypes.optionLocator }], + [ "repeatIf", { + name: "repeat if", + type: TargetTypes.LOCATOR, + description: "Terminate a do control flow branch conditionally. If the result of the provided conditional expression is true, it starts the do loop over. Otherwise it ends the loop.", + target: ArgTypes.conditionalExpression + }], [ "run", { name: "run", description: "Runs a test case from the current project.", @@ -629,6 +611,12 @@ class CommandList { for forms without submit buttons, e.g. single-input \"Search\" forms.", target: ArgTypes.formLocator }], + [ "times", { + name: "times", + type: TargetTypes.LOCATOR, + description: "Create a loop that executes the proceeding commands n number of times.", + target: ArgTypes.times + }], [ "type", { name: "type", type: TargetTypes.LOCATOR, @@ -641,6 +629,12 @@ class CommandList { target: ArgTypes.locator, value: ArgTypes.value }], + [ "uncheck", { + name: "uncheck", + type: TargetTypes.LOCATOR, + description: "Uncheck a toggle-button (checkbox/radio).", + target: ArgTypes.locator + }], [ "verifyChecked", { name: "verify checked", type: TargetTypes.LOCATOR, @@ -759,6 +753,12 @@ class CommandList { instructs Selenium to accept it. If the alert has not \ appeared yet then use \"choose ok on next confirmation\" \ instead." + }], + [ "while", { + name: "while", + type: TargetTypes.LOCATOR, + description: "Create a loop that executes the proceeding commands repeatedly for as long as the provided conditional expression is true.", + target: ArgTypes.conditionalExpression }] ]) @@ -792,12 +792,12 @@ class CommandList { export const Commands = CommandList.instance; export const ControlFlowCommandNames = { - do: Commands.list.get("controlFlowDo").name, - else: Commands.list.get("controlFlowElse").name, - elseIf: Commands.list.get("controlFlowElseIf").name, - end: Commands.list.get("controlFlowEnd").name, - if: Commands.list.get("controlFlowIf").name, - repeatIf: Commands.list.get("controlFlowRepeatIf").name, - times: Commands.list.get("controlFlowTimes").name, - while: Commands.list.get("controlFlowWhile").name + do: Commands.list.get("do").name, + else: Commands.list.get("else").name, + elseIf: Commands.list.get("elseIf").name, + end: Commands.list.get("end").name, + if: Commands.list.get("if").name, + repeatIf: Commands.list.get("repeatIf").name, + times: Commands.list.get("times").name, + while: Commands.list.get("while").name }; diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index f0d97c2dd..91da205fb 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -15,6 +15,8 @@ // specific language governing permissions and limitations // under the License. +import { ControlFlowCommandNames } from "../../models/Command"; + export class CommandNode { constructor(command) { this.command = command; @@ -38,7 +40,7 @@ export class CommandNode { playbackTree.currentCommandNode = this.left; } } else { - if (this.command.command === "end") { + if (this.command.command === ControlFlowCommandNames.end) { playbackTree.currentCommandNode = this.next; } else { extCommand.sendMessage(this.command, this.command.target, this.command.value, false); diff --git a/packages/selianize/src/command.js b/packages/selianize/src/command.js index 6eed6f230..3bdd35b2f 100644 --- a/packages/selianize/src/command.js +++ b/packages/selianize/src/command.js @@ -94,14 +94,14 @@ const emitters = { chooseCancelOnNextPrompt: skip, chooseOkOnNextConfirmation: skip, setSpeed: skip, - controlFlowDo: emitControlFlow, - controlFlowElse: emitControlFlow, - controlFlowElseIf: emitControlFlow, - controlFlowEnd: emitControlFlow, - controlFlowIf: emitControlFlow, - controlFlowRepeatIf: emitControlFlow, - controlFlowTimes: emitControlFlow, - controlFlowWhile: emitControlFlow + do: emitControlFlow, + else: emitControlFlow, + elseIf: emitControlFlow, + end: emitControlFlow, + if: emitControlFlow, + repeatIf: emitControlFlow, + times: emitControlFlow, + while: emitControlFlow }; export function emit(command) { From 306df6465baf04d1f4d7ceabd844378ff251b03c Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 06:15:15 -0400 Subject: [PATCH 040/125] Abstracted the command equality check into its own function --- .../src/neo/playback/playback-tree/index.js | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index f2950f386..7eacafcdf 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -33,30 +33,34 @@ function createCommandNodesFromCommandStack(commandStack) { return connectCommandNodes(initNodes); } +function commandNamesEqual(command, target) { + return (command.command === target); +} + function isDo(command) { - return (command.command === ControlFlowCommandNames.do); + return commandNamesEqual(command, ControlFlowCommandNames.do); } function isElse(command) { - return (command.command === ControlFlowCommandNames.else); + return commandNamesEqual(command, ControlFlowCommandNames.else); } function isElseOrElseIf(command) { - return (command.command === ControlFlowCommandNames.else || - command.command === ControlFlowCommandNames.elseIf); + return (commandNamesEqual(command, ControlFlowCommandNames.else) || + commandNamesEqual(command, ControlFlowCommandNames.elseIf)); } function isEnd(command) { - return (command.command === ControlFlowCommandNames.end); + return (commandNamesEqual(command, ControlFlowCommandNames.end)); } function isIf(command) { - return (command.command === ControlFlowCommandNames.if); + return (commandNamesEqual(command, ControlFlowCommandNames.if)); } function isLoop(command) { - return (command.command === ControlFlowCommandNames.while || - command.command === ControlFlowCommandNames.times); + return (commandNamesEqual(command, ControlFlowCommandNames.while) || + commandNamesEqual(command, ControlFlowCommandNames.times)); } function isEmpty(obj) { From 21ab5be340bea19a48fbb63d6f992a4f43aad57b Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 06:49:47 -0400 Subject: [PATCH 041/125] Changed syntax validation to throw SyntaxError --- .../src/neo/playback/playback-tree/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 7eacafcdf..20a89b5b5 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -88,7 +88,7 @@ function verifyControlFlowSyntax(commandStack) { } }); if (!isEmpty(state)) { - throw "Incomplete block at " + topOf(state).command; + throw new SyntaxError("Incomplete block at " + topOf(state).command); } else { return true; } @@ -111,7 +111,7 @@ function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { function verifyElse (commandName, commandIndex, stack, state) { if (!isIf(topOf(state))) { - throw "An else / else if used outside of an if block"; + throw new SyntaxError("An else / else if used outside of an if block"); } } @@ -123,20 +123,20 @@ function verifyEnd (commandName, commandIndex, stack, state) { const allElses = stack.slice(topOf(state).index, commandIndex).filter( command => (command.command === ControlFlowCommandNames.else || command.command === ControlFlowCommandNames.elseIf)); if (numberOfElse > 1) { - throw "Too many else commands used"; + throw new SyntaxError("Too many else commands used"); } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { - throw "Incorrect command order of else if / else"; + throw new SyntaxError("Incorrect command order of else if / else"); } else if (numberOfElse === 0 || isElse(topOf(allElses))) { state.pop(); } } else { - throw "Use of end without an opening keyword"; + throw new SyntaxError("Use of end without an opening keyword"); } } function verifyRepeatIf (commandName, commandIndex, stack, state) { if (!isDo(topOf(state))) { - throw "A repeat if used without a do block"; + throw new SyntaxError("A repeat if used without a do block"); } state.pop(); } From dba1bef61dc634aa1701d14ddaf64577d7bd93dd Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 07:00:09 -0400 Subject: [PATCH 042/125] Made the leveling functions pure. --- .../src/neo/playback/playback-tree/index.js | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 20a89b5b5..cdfdb304d 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -146,9 +146,12 @@ function deriveCommandLevels(commandStack) { let levels = []; commandStack.forEach(function(command) { if (levelCommand[command.command]) { - level = levelCommand[command.command](command, level, levels); + let result = levelCommand[command.command](command, level, levels); + level = result.level; + levels = result.levels; } else { - levelDefault(command, level, levels); + let result = levelDefault(command, level, levels); + levels = result.levels; } }); return levels; @@ -165,28 +168,32 @@ let levelCommand = { [ControlFlowCommandNames.while]: levelBranchOpen }; -function levelDefault (command, level, levels) { +function levelDefault (command, level, _levels) { + let levels = [ ..._levels ]; levels.push(level); - return level; + return { level, levels }; } -function levelBranchOpen (command, level, levels) { +function levelBranchOpen (command, level, _levels) { + let levels = [ ..._levels ]; levels.push(level); level++; - return level; + return { level, levels }; } -function levelBranchEnd (command, level, levels) { +function levelBranchEnd (command, level, _levels) { + let levels = [ ..._levels ]; level--; levels.push(level); - return level; + return { level, levels }; } -function levelElse (command, level, levels) { +function levelElse (command, level, _levels) { + let levels = [ ..._levels ]; level--; levels.push(level); level++; - return level; + return { level, levels }; } function initCommandNodes(commandStack, levels) { From b84528cdaaa3fb08d539bbe8ccf60236b2bab52d Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 07:24:39 -0400 Subject: [PATCH 043/125] Pulled the push down automata (a.k.a. the state array) into its own class with appropriate setters and getters. --- .../__test__/playback/playback-tree.spec.js | 6 +-- .../src/neo/playback/playback-tree/index.js | 51 +++++++++---------- .../src/neo/playback/playback-tree/state.js | 39 ++++++++++++++ 3 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/state.js diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 7375e9239..60bd549ef 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -189,15 +189,15 @@ describe("Control Flow", () => { let input = [createCommand("times")]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at times"); }); - test(ControlFlowCommandNames.repeatIf, () => { + test("repeatIf", () => { let input = [createCommand(ControlFlowCommandNames.repeatIf)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("A repeat if used without a do block"); }); - test(ControlFlowCommandNames.do, () => { + test("do", () => { let input = [createCommand(ControlFlowCommandNames.do)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at do"); }); - test(ControlFlowCommandNames.end, () => { + test("end", () => { let input = [createCommand(ControlFlowCommandNames.end)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Use of end without an opening keyword"); }); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index cdfdb304d..0d2a63161 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -17,6 +17,7 @@ import { CommandNode } from "./command-node"; import { PlaybackTree } from "./playback-tree"; +import { State } from "./state"; import { ControlFlowCommandNames } from "../../models/Command"; export { createPlaybackTree }; // public API export { createCommandNodesFromCommandStack, verifyControlFlowSyntax, deriveCommandLevels }; // for testing @@ -34,7 +35,11 @@ function createCommandNodesFromCommandStack(commandStack) { } function commandNamesEqual(command, target) { - return (command.command === target); + if (command) { + return (command.command === target); + } else { + return false; + } } function isDo(command) { @@ -63,14 +68,6 @@ function isLoop(command) { commandNamesEqual(command, ControlFlowCommandNames.times)); } -function isEmpty(obj) { - if (obj) { - return (obj.length === 0); - } else { - return false; - } -} - function topOf(array) { let arr = array[array.length - 1]; if (arr) { @@ -81,14 +78,14 @@ function topOf(array) { } function verifyControlFlowSyntax(commandStack) { - let state = []; + let state = new State; commandStack.forEach(function(command, commandIndex) { if (verifyCommand[command.command]) { verifyCommand[command.command](command.command, commandIndex, commandStack, state); } }); - if (!isEmpty(state)) { - throw new SyntaxError("Incomplete block at " + topOf(state).command); + if (!state.empty()) { + throw new SyntaxError("Incomplete block at " + state.top().command); } else { return true; } @@ -110,17 +107,17 @@ function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { } function verifyElse (commandName, commandIndex, stack, state) { - if (!isIf(topOf(state))) { + if (!isIf(state.top())) { throw new SyntaxError("An else / else if used outside of an if block"); } } -function verifyEnd (commandName, commandIndex, stack, state) { - if (isLoop(topOf(state))) { +function verifyEnd (command, commandIndex, stack, state) { + if (isLoop(state.top())) { state.pop(); - } else if (isIf(topOf(state))) { - const numberOfElse = stack.slice(topOf(state).index, commandIndex).filter(command => isElse(command)).length; - const allElses = stack.slice(topOf(state).index, commandIndex).filter( + } else if (isIf(state.top())) { + const numberOfElse = stack.slice(state.top().index, commandIndex).filter(command => isElse(command)).length; + const allElses = stack.slice(state.top().index, commandIndex).filter( command => (command.command === ControlFlowCommandNames.else || command.command === ControlFlowCommandNames.elseIf)); if (numberOfElse > 1) { throw new SyntaxError("Too many else commands used"); @@ -135,7 +132,7 @@ function verifyEnd (commandName, commandIndex, stack, state) { } function verifyRepeatIf (commandName, commandIndex, stack, state) { - if (!isDo(topOf(state))) { + if (!isDo(state.top())) { throw new SyntaxError("A repeat if used without a do block"); } state.pop(); @@ -209,7 +206,7 @@ function initCommandNodes(commandStack, levels) { function connectCommandNodes(commandNodeStack) { let _commandNodeStack = [ ...commandNodeStack ]; - let state = []; + let state = new State; _commandNodeStack.forEach(function(commandNode) { let nextCommandNode = _commandNodeStack[commandNode.index + 1]; if (nextCommandNode) { @@ -236,10 +233,10 @@ let connectCommandNode = { function connectDefault (commandNode, nextCommandNode, stack, state) { let _nextCommandNode; - if (isIf(topOf(state)) && (isElseOrElseIf(nextCommandNode.command))) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, ControlFlowCommandNames.end); - } else if (isLoop(topOf(state)) && isEnd(nextCommandNode.command)) { - _nextCommandNode = stack[topOf(state).index]; + if (isIf(state.top()) && (isElseOrElseIf(nextCommandNode.command))) { + _nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); + } else if (isLoop(state.top()) && isEnd(nextCommandNode.command)) { + _nextCommandNode = stack[state.top().index]; } else { _nextCommandNode = nextCommandNode; } @@ -266,17 +263,17 @@ function connectNext (commandNode, nextCommandNode) { } function connectRepeatIf (commandNode, nextCommandNode, stack, state) { - commandNode.right = stack[topOf(state).index]; + commandNode.right = stack[state.top().index]; commandNode.left = nextCommandNode; state.pop(); } function connectEnd (commandNode, nextCommandNode, stack, state) { state.pop(); - if (!isEmpty(state)) { + if (!state.empty()) { let _nextCommandNode; if (isElseOrElseIf(nextCommandNode.command)) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, topOf(state).level, ControlFlowCommandNames.end); + _nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); } else { _nextCommandNode = nextCommandNode; } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/state.js b/packages/selenium-ide/src/neo/playback/playback-tree/state.js new file mode 100644 index 000000000..917ba0976 --- /dev/null +++ b/packages/selenium-ide/src/neo/playback/playback-tree/state.js @@ -0,0 +1,39 @@ +// 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 class State { + constructor() { + this._state = []; + } + + push(obj) { + this._state.push(obj); + } + + pop() { + this._state.pop(); + } + + empty() { + return (this._state.length === 0); + } + + top() { + return this._state[this._state.length - 1]; + } +} + From 2b7053d23f3eca5b8f3e1f74f6d1f28c3607eae3 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 09:06:20 -0400 Subject: [PATCH 044/125] Added #empty to state --- .../selenium-ide/src/neo/playback/playback-tree/state.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/state.js b/packages/selenium-ide/src/neo/playback/playback-tree/state.js index 917ba0976..913269d32 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/state.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/state.js @@ -20,6 +20,10 @@ export class State { this._state = []; } + empty() { + return (this._state.length === 0); + } + push(obj) { this._state.push(obj); } @@ -28,10 +32,6 @@ export class State { this._state.pop(); } - empty() { - return (this._state.length === 0); - } - top() { return this._state[this._state.length - 1]; } From aa270e835073465120dbd2a747aaeb11fb1446d5 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 09:07:11 -0400 Subject: [PATCH 045/125] Cleaned up the code around verifying else and else if syntax. --- .../__test__/playback/playback-tree.spec.js | 26 ++++---- .../src/neo/playback/playback-tree/index.js | 63 ++++++++++--------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 60bd549ef..e8a773b47 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -75,7 +75,7 @@ describe("Control Flow", () => { test("if, elseIf, end", () => { let result = verifyControlFlowSyntax([ createCommand(ControlFlowCommandNames.if), - createCommand("elseIf"), + createCommand(ControlFlowCommandNames.elseIf), createCommand(ControlFlowCommandNames.end) ]); expect(result).toBeTruthy(); @@ -83,7 +83,7 @@ describe("Control Flow", () => { test("if, elseIf, else, end", () => { let result = verifyControlFlowSyntax([ createCommand(ControlFlowCommandNames.if), - createCommand("elseIf"), + createCommand(ControlFlowCommandNames.elseIf), createCommand(ControlFlowCommandNames.else), createCommand(ControlFlowCommandNames.end) ]); @@ -137,7 +137,7 @@ describe("Control Flow", () => { let input = [ createCommand(ControlFlowCommandNames.if), createCommand(ControlFlowCommandNames.else), - createCommand("elseIf"), + createCommand(ControlFlowCommandNames.elseIf), createCommand(ControlFlowCommandNames.end) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incorrect command order of else if / else"); @@ -151,15 +151,21 @@ describe("Control Flow", () => { ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Too many else commands used"); }); - test(ControlFlowCommandNames.while, () => { + test("else", () => { + let input = [ createCommand(ControlFlowCommandNames.else) ]; + expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); + }); + test("elseIf", () => { + let input = [ createCommand(ControlFlowCommandNames.elseIf) ]; + expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else if used outside of an if block"); + }); + test("while", () => { let input = [createCommand(ControlFlowCommandNames.while)]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); }); test("if, while", () => { let input = [ createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand("elseIf"), createCommand(ControlFlowCommandNames.while) ]; expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); @@ -167,8 +173,6 @@ describe("Control Flow", () => { test("if, while, end", () => { let input = [ createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand("elseIf"), createCommand(ControlFlowCommandNames.while), createCommand(ControlFlowCommandNames.end) ]; @@ -177,13 +181,11 @@ describe("Control Flow", () => { test("if, while, else, end", () => { let input = [ createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand("elseIf"), createCommand(ControlFlowCommandNames.while), createCommand(ControlFlowCommandNames.else), createCommand(ControlFlowCommandNames.end) ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else / else if used outside of an if block"); + expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); }); test("times", () => { let input = [createCommand("times")]; @@ -230,7 +232,7 @@ describe("Control Flow", () => { let input = [ createCommand(ControlFlowCommandNames.if), createCommand("command"), - createCommand("elseIf"), + createCommand(ControlFlowCommandNames.elseIf), createCommand("command"), createCommand(ControlFlowCommandNames.else), createCommand("command"), diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 0d2a63161..2ce5d96a1 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -42,6 +42,10 @@ function commandNamesEqual(command, target) { } } +function isConditional(command) { + return (isIf(command) || isElseOrElseIf(command)); +} + function isDo(command) { return commandNamesEqual(command, ControlFlowCommandNames.do); } @@ -68,20 +72,15 @@ function isLoop(command) { commandNamesEqual(command, ControlFlowCommandNames.times)); } -function topOf(array) { - let arr = array[array.length - 1]; - if (arr) { - return arr; - } else { - return { }; - } +function isBlockOpen(command) { + return (isIf(command) || isLoop(command)); } function verifyControlFlowSyntax(commandStack) { let state = new State; - commandStack.forEach(function(command, commandIndex) { + commandStack.forEach(function(command) { if (verifyCommand[command.command]) { - verifyCommand[command.command](command.command, commandIndex, commandStack, state); + verifyCommand[command.command](command.command, state); } }); if (!state.empty()) { @@ -94,7 +93,7 @@ function verifyControlFlowSyntax(commandStack) { const verifyCommand = { [ControlFlowCommandNames.do]: trackControlFlowBranchOpen, [ControlFlowCommandNames.else]: verifyElse, - [ControlFlowCommandNames.elseIf]: verifyElse, + [ControlFlowCommandNames.elseIf]: verifyElseIf, [ControlFlowCommandNames.end]: verifyEnd, [ControlFlowCommandNames.if]: trackControlFlowBranchOpen, [ControlFlowCommandNames.repeatIf]: verifyRepeatIf, @@ -102,36 +101,42 @@ const verifyCommand = { [ControlFlowCommandNames.while]: trackControlFlowBranchOpen }; -function trackControlFlowBranchOpen (commandName, commandIndex, stack, state) { - state.push({ command: commandName, index: commandIndex }); +function trackControlFlowBranchOpen (commandName, state) { + state.push({ command: commandName }); +} + +function verifyElse (commandName, state) { + if (!isConditional(state.top())) { + throw new SyntaxError("An else used outside of an if block"); + } + if (isElse(state.top())) { + throw new SyntaxError("Too many else commands used"); + } + state.push({ command: commandName }); } -function verifyElse (commandName, commandIndex, stack, state) { - if (!isIf(state.top())) { - throw new SyntaxError("An else / else if used outside of an if block"); +function verifyElseIf (commandName, state) { + if (!isConditional(state.top())) { + throw new SyntaxError("An else if used outside of an if block"); } + if (isElse(state.top())) { + throw new SyntaxError("Incorrect command order of else if / else"); + } + state.push({ command: commandName }); } -function verifyEnd (command, commandIndex, stack, state) { - if (isLoop(state.top())) { +function verifyEnd (command, state) { + if (isBlockOpen(state.top())) { state.pop(); - } else if (isIf(state.top())) { - const numberOfElse = stack.slice(state.top().index, commandIndex).filter(command => isElse(command)).length; - const allElses = stack.slice(state.top().index, commandIndex).filter( - command => (command.command === ControlFlowCommandNames.else || command.command === ControlFlowCommandNames.elseIf)); - if (numberOfElse > 1) { - throw new SyntaxError("Too many else commands used"); - } else if (numberOfElse === 1 && !isElse(topOf(allElses))) { - throw new SyntaxError("Incorrect command order of else if / else"); - } else if (numberOfElse === 0 || isElse(topOf(allElses))) { - state.pop(); - } + } else if (isElseOrElseIf(state.top())) { + state.pop(); + verifyEnd(command, state); } else { throw new SyntaxError("Use of end without an opening keyword"); } } -function verifyRepeatIf (commandName, commandIndex, stack, state) { +function verifyRepeatIf (commandName, state) { if (!isDo(state.top())) { throw new SyntaxError("A repeat if used without a do block"); } From 207c291ec90daaad72e4a0683836bf17f62742f9 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 09:47:19 -0400 Subject: [PATCH 046/125] Broke syntax validaiton and command leveling into separate files and moved test coverage along with it. Also moved control flow command checks (e.g., isEnd, isLoop, etc.) into Command and exported them through a ControlFlowCommandChecks object. --- .../__test__/playback/command-leveler.spec.js | 60 +++++ .../__test__/playback/playback-tree.spec.js | 184 +------------- .../__test__/playback/syntax-validation.js | 176 +++++++++++++ .../selenium-ide/src/neo/models/Command.js | 54 ++++ .../playback/playback-tree/command-leveler.js | 73 ++++++ .../playback/playback-tree/command-node.js | 4 +- .../src/neo/playback/playback-tree/index.js | 232 +++--------------- .../playback-tree/syntax-validation.js | 86 +++++++ 8 files changed, 487 insertions(+), 382 deletions(-) create mode 100644 packages/selenium-ide/src/neo/__test__/playback/command-leveler.spec.js create mode 100644 packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/command-leveler.js create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js diff --git a/packages/selenium-ide/src/neo/__test__/playback/command-leveler.spec.js b/packages/selenium-ide/src/neo/__test__/playback/command-leveler.spec.js new file mode 100644 index 000000000..013838318 --- /dev/null +++ b/packages/selenium-ide/src/neo/__test__/playback/command-leveler.spec.js @@ -0,0 +1,60 @@ +// 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 Command, { ControlFlowCommandNames } from "../../models/Command"; +import { deriveCommandLevels } from "../../playback/playback-tree/command-leveler"; + +function createCommand(name) { + return new Command(null, name, "", ""); +} + +describe("Control Flow", () => { + describe("Preprocess", () => { + describe("Leveling", () => { + test("returns leveled command stack", () => { + let stack = deriveCommandLevels([ + createCommand(ControlFlowCommandNames.if), + createCommand("command"), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.do), + createCommand("command"), + createCommand(ControlFlowCommandNames.while), + createCommand("command"), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf), + createCommand(ControlFlowCommandNames.end) + ]); + expect(stack[0]).toEqual(0); // if + expect(stack[1]).toEqual(1); // command + expect(stack[2]).toEqual(0); // else + expect(stack[3]).toEqual(1); // while + expect(stack[4]).toEqual(2); // command + expect(stack[5]).toEqual(1); // end + expect(stack[6]).toEqual(1); // do + expect(stack[7]).toEqual(2); // command + expect(stack[8]).toEqual(2); // while + expect(stack[9]).toEqual(3); // command + expect(stack[10]).toEqual(2); // end + expect(stack[11]).toEqual(1); // repeatIf + expect(stack[12]).toEqual(0); // end + }); + }); + }); +}); diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index e8a773b47..ab0aea5ea 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { createPlaybackTree, deriveCommandLevels, verifyControlFlowSyntax, createCommandNodesFromCommandStack } from "../../playback/playback-tree"; +import { createPlaybackTree, createCommandNodesFromCommandStack } from "../../playback/playback-tree"; import Command, { ControlFlowCommandNames } from "../../models/Command"; function createCommand(name) { @@ -23,188 +23,6 @@ function createCommand(name) { } describe("Control Flow", () => { - describe("Preprocess", () => { - describe("Leveling", () => { - test("returns leveled command stack", () => { - let stack = deriveCommandLevels([ - createCommand(ControlFlowCommandNames.if), - createCommand("command"), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.while), - createCommand("command"), - createCommand(ControlFlowCommandNames.end), - createCommand(ControlFlowCommandNames.do), - createCommand("command"), - createCommand(ControlFlowCommandNames.while), - createCommand("command"), - createCommand(ControlFlowCommandNames.end), - createCommand(ControlFlowCommandNames.repeatIf), - createCommand(ControlFlowCommandNames.end) - ]); - expect(stack[0]).toEqual(0); // if - expect(stack[1]).toEqual(1); // command - expect(stack[2]).toEqual(0); // else - expect(stack[3]).toEqual(1); // while - expect(stack[4]).toEqual(2); // command - expect(stack[5]).toEqual(1); // end - expect(stack[6]).toEqual(1); // do - expect(stack[7]).toEqual(2); // command - expect(stack[8]).toEqual(2); // while - expect(stack[9]).toEqual(3); // command - expect(stack[10]).toEqual(2); // end - expect(stack[11]).toEqual(1); // repeatIf - expect(stack[12]).toEqual(0); // end - }); - }); - describe("Syntax Validation", () => { - test("if, end", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("if, else, end", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("if, elseIf, end", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.elseIf), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("if, elseIf, else, end", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.elseIf), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("while, end", () => { - let result = new verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.while), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("times, end", () => { - let result = verifyControlFlowSyntax([ - createCommand("times"), - createCommand(ControlFlowCommandNames.end) - ]); - expect(result).toBeTruthy(); - }); - test("do, repeatIf", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.do), - createCommand(ControlFlowCommandNames.repeatIf) - ]); - expect(result).toBeTruthy(); - }); - test("do, while, end, repeatIf", () => { - let result = verifyControlFlowSyntax([ - createCommand(ControlFlowCommandNames.do), - createCommand(ControlFlowCommandNames.while), - createCommand(ControlFlowCommandNames.end), - createCommand(ControlFlowCommandNames.repeatIf) - ]); - expect(result).toBeTruthy(); - }); - }); - describe("Syntax Invalidation", () => { - test("if", () => { - let input = [createCommand(ControlFlowCommandNames.if)]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); - }); - test("if, if, end", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.end) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); - }); - test("if, else, elseIf, end", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.elseIf), - createCommand(ControlFlowCommandNames.end) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incorrect command order of else if / else"); - }); - test("if, else, else, end", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.end) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Too many else commands used"); - }); - test("else", () => { - let input = [ createCommand(ControlFlowCommandNames.else) ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); - }); - test("elseIf", () => { - let input = [ createCommand(ControlFlowCommandNames.elseIf) ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else if used outside of an if block"); - }); - test("while", () => { - let input = [createCommand(ControlFlowCommandNames.while)]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); - }); - test("if, while", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.while) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at while"); - }); - test("if, while, end", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.while), - createCommand(ControlFlowCommandNames.end) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at if"); - }); - test("if, while, else, end", () => { - let input = [ - createCommand(ControlFlowCommandNames.if), - createCommand(ControlFlowCommandNames.while), - createCommand(ControlFlowCommandNames.else), - createCommand(ControlFlowCommandNames.end) - ]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); - }); - test("times", () => { - let input = [createCommand("times")]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at times"); - }); - test("repeatIf", () => { - let input = [createCommand(ControlFlowCommandNames.repeatIf)]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("A repeat if used without a do block"); - }); - test("do", () => { - let input = [createCommand(ControlFlowCommandNames.do)]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Incomplete block at do"); - }); - test("end", () => { - let input = [createCommand(ControlFlowCommandNames.end)]; - expect(function() { verifyControlFlowSyntax(input); }).toThrow("Use of end without an opening keyword"); - }); - }); - }); describe("Process", () => { describe("Linked List Validation", () => { test("nodes contain command references and levels", () => { diff --git a/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js new file mode 100644 index 000000000..dd087f41a --- /dev/null +++ b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js @@ -0,0 +1,176 @@ +// 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 { validateControlFlowSyntax } from "../../playback/playback-tree/syntax-validation"; +import Command, { ControlFlowCommandNames } from "../../models/Command"; + +function createCommand(name) { + return new Command(null, name, "", ""); +} + +describe("Control Flow", () => { + describe("Preprocess", () => { + describe("Syntax Validation", () => { + test("if, end", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("if, else, end", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("if, elseIf, end", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.elseIf), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("if, elseIf, else, end", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.elseIf), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("while, end", () => { + let result = new validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("times, end", () => { + let result = validateControlFlowSyntax([ + createCommand("times"), + createCommand(ControlFlowCommandNames.end) + ]); + expect(result).toBeTruthy(); + }); + test("do, repeatIf", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.do), + createCommand(ControlFlowCommandNames.repeatIf) + ]); + expect(result).toBeTruthy(); + }); + test("do, while, end, repeatIf", () => { + let result = validateControlFlowSyntax([ + createCommand(ControlFlowCommandNames.do), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.repeatIf) + ]); + expect(result).toBeTruthy(); + }); + }); + describe("Syntax Invalidation", () => { + test("if", () => { + let input = [createCommand(ControlFlowCommandNames.if)]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at if"); + }); + test("if, if, end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.end) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at if"); + }); + test("if, else, elseIf, end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.elseIf), + createCommand(ControlFlowCommandNames.end) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incorrect command order of else if / else"); + }); + test("if, else, else, end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Too many else commands used"); + }); + test("else", () => { + let input = [ createCommand(ControlFlowCommandNames.else) ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); + }); + test("elseIf", () => { + let input = [ createCommand(ControlFlowCommandNames.elseIf) ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("An else if used outside of an if block"); + }); + test("while", () => { + let input = [createCommand(ControlFlowCommandNames.while)]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at while"); + }); + test("if, while", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.while) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at while"); + }); + test("if, while, end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at if"); + }); + test("if, while, else, end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.end) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); + }); + test("times", () => { + let input = [createCommand("times")]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at times"); + }); + test("repeatIf", () => { + let input = [createCommand(ControlFlowCommandNames.repeatIf)]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("A repeat if used without a do block"); + }); + test("do", () => { + let input = [createCommand(ControlFlowCommandNames.do)]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Incomplete block at do"); + }); + test("end", () => { + let input = [createCommand(ControlFlowCommandNames.end)]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("Use of end without an opening keyword"); + }); + }); + }); +}); diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 4ba91cdea..eeff4273e 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -801,3 +801,57 @@ export const ControlFlowCommandNames = { times: Commands.list.get("times").name, while: Commands.list.get("while").name }; + +function commandNamesEqual(command, target) { + if (command) { + return (command.command === target); + } else { + return false; + } +} + +function isConditional(command) { + return (isIf(command) || isElseOrElseIf(command)); +} + +function isDo(command) { + return commandNamesEqual(command, ControlFlowCommandNames.do); +} + +function isElse(command) { + return commandNamesEqual(command, ControlFlowCommandNames.else); +} + +function isElseOrElseIf(command) { + return (commandNamesEqual(command, ControlFlowCommandNames.else) || + commandNamesEqual(command, ControlFlowCommandNames.elseIf)); +} + +function isEnd(command) { + return (commandNamesEqual(command, ControlFlowCommandNames.end)); +} + +function isIf(command) { + return (commandNamesEqual(command, ControlFlowCommandNames.if)); +} + +function isLoop(command) { + return (commandNamesEqual(command, ControlFlowCommandNames.while) || + commandNamesEqual(command, ControlFlowCommandNames.times)); +} + +function isBlockOpen(command) { + return (isIf(command) || isLoop(command)); +} + +export const ControlFlowCommandChecks = { + isConditional: isConditional, + isDo: isDo, + isElse: isElse, + isElseOrElseIf: isElseOrElseIf, + isEnd: isEnd, + isIf: isIf, + isLoop: isLoop, + isBlockOpen: isBlockOpen +}; + diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-leveler.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-leveler.js new file mode 100644 index 000000000..aadfb4ac7 --- /dev/null +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-leveler.js @@ -0,0 +1,73 @@ +// 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 { ControlFlowCommandNames } from "../../models/Command"; + +export function deriveCommandLevels(commandStack) { + let level = 0; + let levels = []; + commandStack.forEach(function(command) { + if (levelCommand[command.command]) { + let result = levelCommand[command.command](command, level, levels); + level = result.level; + levels = result.levels; + } else { + let result = levelDefault(command, level, levels); + levels = result.levels; + } + }); + return levels; +} + +let levelCommand = { + [ControlFlowCommandNames.do]: levelBranchOpen, + [ControlFlowCommandNames.else]: levelElse, + [ControlFlowCommandNames.elseIf]: levelElse, + [ControlFlowCommandNames.end]: levelBranchEnd, + [ControlFlowCommandNames.if]: levelBranchOpen, + [ControlFlowCommandNames.repeatIf]: levelBranchEnd, + [ControlFlowCommandNames.times]: levelBranchOpen, + [ControlFlowCommandNames.while]: levelBranchOpen +}; + +function levelDefault (command, level, _levels) { + let levels = [ ..._levels ]; + levels.push(level); + return { level, levels }; +} + +function levelBranchOpen (command, level, _levels) { + let levels = [ ..._levels ]; + levels.push(level); + level++; + return { level, levels }; +} + +function levelBranchEnd (command, level, _levels) { + let levels = [ ..._levels ]; + level--; + levels.push(level); + return { level, levels }; +} + +function levelElse (command, level, _levels) { + let levels = [ ..._levels ]; + level--; + levels.push(level); + level++; + return { level, levels }; +} diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 91da205fb..96153242e 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -import { ControlFlowCommandNames } from "../../models/Command"; +import { ControlFlowCommandChecks } from "../../models/Command"; export class CommandNode { constructor(command) { @@ -40,7 +40,7 @@ export class CommandNode { playbackTree.currentCommandNode = this.left; } } else { - if (this.command.command === ControlFlowCommandNames.end) { + if (ControlFlowCommandChecks.isEnd(this.command)) { playbackTree.currentCommandNode = this.next; } else { extCommand.sendMessage(this.command, this.command.target, this.command.value, false); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 2ce5d96a1..fff5f0160 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -1,26 +1,28 @@ // 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 +// dControlFlowCommandChecks.istributed with thControlFlowCommandChecks.is work for additional information +// regarding copyright ownership. The SFC licenses thControlFlowCommandChecks.is file // to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance +// "License"); you may not use thControlFlowCommandChecks.is 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 +// software dControlFlowCommandChecks.istributed under the License ControlFlowCommandChecks.is dControlFlowCommandChecks.istributed on an +// "AS ControlFlowCommandChecks.is" BASControlFlowCommandChecks.is, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the -// specific language governing permissions and limitations +// specific language governing permControlFlowCommandChecks.issions and limitations // under the License. import { CommandNode } from "./command-node"; import { PlaybackTree } from "./playback-tree"; import { State } from "./state"; -import { ControlFlowCommandNames } from "../../models/Command"; +import { validateControlFlowSyntax } from "./syntax-validation"; +import { deriveCommandLevels } from "./command-leveler"; +import { ControlFlowCommandNames, ControlFlowCommandChecks } from "../../models/Command"; export { createPlaybackTree }; // public API -export { createCommandNodesFromCommandStack, verifyControlFlowSyntax, deriveCommandLevels }; // for testing +export { createCommandNodesFromCommandStack }; // for testing function createPlaybackTree(commandStack) { let nodes = createCommandNodesFromCommandStack(commandStack); @@ -28,177 +30,13 @@ function createPlaybackTree(commandStack) { } function createCommandNodesFromCommandStack(commandStack) { - verifyControlFlowSyntax(commandStack); + validateControlFlowSyntax(commandStack); let levels = deriveCommandLevels(commandStack); - let initNodes = initCommandNodes(commandStack, levels); - return connectCommandNodes(initNodes); + let nodes = createCommandNodes(commandStack, levels); + return connectCommandNodes(nodes); } -function commandNamesEqual(command, target) { - if (command) { - return (command.command === target); - } else { - return false; - } -} - -function isConditional(command) { - return (isIf(command) || isElseOrElseIf(command)); -} - -function isDo(command) { - return commandNamesEqual(command, ControlFlowCommandNames.do); -} - -function isElse(command) { - return commandNamesEqual(command, ControlFlowCommandNames.else); -} - -function isElseOrElseIf(command) { - return (commandNamesEqual(command, ControlFlowCommandNames.else) || - commandNamesEqual(command, ControlFlowCommandNames.elseIf)); -} - -function isEnd(command) { - return (commandNamesEqual(command, ControlFlowCommandNames.end)); -} - -function isIf(command) { - return (commandNamesEqual(command, ControlFlowCommandNames.if)); -} - -function isLoop(command) { - return (commandNamesEqual(command, ControlFlowCommandNames.while) || - commandNamesEqual(command, ControlFlowCommandNames.times)); -} - -function isBlockOpen(command) { - return (isIf(command) || isLoop(command)); -} - -function verifyControlFlowSyntax(commandStack) { - let state = new State; - commandStack.forEach(function(command) { - if (verifyCommand[command.command]) { - verifyCommand[command.command](command.command, state); - } - }); - if (!state.empty()) { - throw new SyntaxError("Incomplete block at " + state.top().command); - } else { - return true; - } -} - -const verifyCommand = { - [ControlFlowCommandNames.do]: trackControlFlowBranchOpen, - [ControlFlowCommandNames.else]: verifyElse, - [ControlFlowCommandNames.elseIf]: verifyElseIf, - [ControlFlowCommandNames.end]: verifyEnd, - [ControlFlowCommandNames.if]: trackControlFlowBranchOpen, - [ControlFlowCommandNames.repeatIf]: verifyRepeatIf, - [ControlFlowCommandNames.times]: trackControlFlowBranchOpen, - [ControlFlowCommandNames.while]: trackControlFlowBranchOpen -}; - -function trackControlFlowBranchOpen (commandName, state) { - state.push({ command: commandName }); -} - -function verifyElse (commandName, state) { - if (!isConditional(state.top())) { - throw new SyntaxError("An else used outside of an if block"); - } - if (isElse(state.top())) { - throw new SyntaxError("Too many else commands used"); - } - state.push({ command: commandName }); -} - -function verifyElseIf (commandName, state) { - if (!isConditional(state.top())) { - throw new SyntaxError("An else if used outside of an if block"); - } - if (isElse(state.top())) { - throw new SyntaxError("Incorrect command order of else if / else"); - } - state.push({ command: commandName }); -} - -function verifyEnd (command, state) { - if (isBlockOpen(state.top())) { - state.pop(); - } else if (isElseOrElseIf(state.top())) { - state.pop(); - verifyEnd(command, state); - } else { - throw new SyntaxError("Use of end without an opening keyword"); - } -} - -function verifyRepeatIf (commandName, state) { - if (!isDo(state.top())) { - throw new SyntaxError("A repeat if used without a do block"); - } - state.pop(); -} - -function deriveCommandLevels(commandStack) { - let level = 0; - let levels = []; - commandStack.forEach(function(command) { - if (levelCommand[command.command]) { - let result = levelCommand[command.command](command, level, levels); - level = result.level; - levels = result.levels; - } else { - let result = levelDefault(command, level, levels); - levels = result.levels; - } - }); - return levels; -} - -let levelCommand = { - [ControlFlowCommandNames.do]: levelBranchOpen, - [ControlFlowCommandNames.else]: levelElse, - [ControlFlowCommandNames.elseIf]: levelElse, - [ControlFlowCommandNames.end]: levelBranchEnd, - [ControlFlowCommandNames.if]: levelBranchOpen, - [ControlFlowCommandNames.repeatIf]: levelBranchEnd, - [ControlFlowCommandNames.times]: levelBranchOpen, - [ControlFlowCommandNames.while]: levelBranchOpen -}; - -function levelDefault (command, level, _levels) { - let levels = [ ..._levels ]; - levels.push(level); - return { level, levels }; -} - -function levelBranchOpen (command, level, _levels) { - let levels = [ ..._levels ]; - levels.push(level); - level++; - return { level, levels }; -} - -function levelBranchEnd (command, level, _levels) { - let levels = [ ..._levels ]; - level--; - levels.push(level); - return { level, levels }; -} - -function levelElse (command, level, _levels) { - let levels = [ ..._levels ]; - level--; - levels.push(level); - level++; - return { level, levels }; -} - -function initCommandNodes(commandStack, levels) { +function createCommandNodes(commandStack, levels) { let commandNodes = []; commandStack.forEach(function(command, index) { let node = new CommandNode(command); @@ -209,20 +47,20 @@ function initCommandNodes(commandStack, levels) { return commandNodes; } -function connectCommandNodes(commandNodeStack) { - let _commandNodeStack = [ ...commandNodeStack ]; +function connectCommandNodes(_commandNodeStack) { + let commandNodeStack = [ ..._commandNodeStack ]; let state = new State; - _commandNodeStack.forEach(function(commandNode) { - let nextCommandNode = _commandNodeStack[commandNode.index + 1]; + commandNodeStack.forEach(function(commandNode) { + let nextCommandNode = commandNodeStack[commandNode.index + 1]; if (nextCommandNode) { if (connectCommandNode[commandNode.command.command]) { - connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, _commandNodeStack, state); + connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, commandNodeStack, state); } else { - connectDefault(commandNode, nextCommandNode, _commandNodeStack, state); + connectDefault(commandNode, nextCommandNode, commandNodeStack, state); } } }); - return _commandNodeStack; + return commandNodeStack; } let connectCommandNode = { @@ -236,16 +74,16 @@ let connectCommandNode = { [ControlFlowCommandNames.while]: connectConditionalForBranchOpen }; -function connectDefault (commandNode, nextCommandNode, stack, state) { - let _nextCommandNode; - if (isIf(state.top()) && (isElseOrElseIf(nextCommandNode.command))) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); - } else if (isLoop(state.top()) && isEnd(nextCommandNode.command)) { - _nextCommandNode = stack[state.top().index]; +function connectDefault (commandNode, _nextCommandNode, stack, state) { + let nextCommandNode; + if (ControlFlowCommandChecks.isIf(state.top()) && (ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command))) { + nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); + } else if (ControlFlowCommandChecks.isLoop(state.top()) && ControlFlowCommandChecks.isEnd(_nextCommandNode.command)) { + nextCommandNode = stack[state.top().index]; } else { - _nextCommandNode = nextCommandNode; + nextCommandNode = _nextCommandNode; } - connectNext(commandNode, _nextCommandNode); + connectNext(commandNode, nextCommandNode); } function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, state) { @@ -273,16 +111,16 @@ function connectRepeatIf (commandNode, nextCommandNode, stack, state) { state.pop(); } -function connectEnd (commandNode, nextCommandNode, stack, state) { +function connectEnd (commandNode, _nextCommandNode, stack, state) { state.pop(); if (!state.empty()) { - let _nextCommandNode; - if (isElseOrElseIf(nextCommandNode.command)) { - _nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); + let nextCommandNode; + if (ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command)) { + nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); } else { - _nextCommandNode = nextCommandNode; + nextCommandNode = _nextCommandNode; } - connectNext(commandNode, _nextCommandNode); + connectNext(commandNode, nextCommandNode); } } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js b/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js new file mode 100644 index 000000000..f6b65fa63 --- /dev/null +++ b/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js @@ -0,0 +1,86 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// dControlFlowCommandChecks.istributed with thControlFlowCommandChecks.is work for additional information +// regarding copyright ownership. The SFC licenses thControlFlowCommandChecks.is file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use thControlFlowCommandChecks.is 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 dControlFlowCommandChecks.istributed under the License ControlFlowCommandChecks.is dControlFlowCommandChecks.istributed on an +// "AS ControlFlowCommandChecks.is" BASControlFlowCommandChecks.is, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permControlFlowCommandChecks.issions and limitations +// under the License. + +import { ControlFlowCommandNames, ControlFlowCommandChecks } from "../../models/Command"; +import { State } from "./state"; + +export function validateControlFlowSyntax(commandStack) { + let state = new State; + commandStack.forEach(function(command) { + if (validateCommand[command.command]) { + validateCommand[command.command](command.command, state); + } + }); + if (!state.empty()) { + throw new SyntaxError("Incomplete block at " + state.top().command); + } else { + return true; + } +} + +const validateCommand = { + [ControlFlowCommandNames.do]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.else]: validateElse, + [ControlFlowCommandNames.elseIf]: validateElseIf, + [ControlFlowCommandNames.end]: validateEnd, + [ControlFlowCommandNames.if]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.repeatIf]: validateRepeatIf, + [ControlFlowCommandNames.times]: trackControlFlowBranchOpen, + [ControlFlowCommandNames.while]: trackControlFlowBranchOpen +}; + +function trackControlFlowBranchOpen (commandName, state) { + state.push({ command: commandName }); +} + +function validateElse (commandName, state) { + if (!ControlFlowCommandChecks.isConditional(state.top())) { + throw new SyntaxError("An else used outside of an if block"); + } + if (ControlFlowCommandChecks.isElse(state.top())) { + throw new SyntaxError("Too many else commands used"); + } + state.push({ command: commandName }); +} + +function validateElseIf (commandName, state) { + if (!ControlFlowCommandChecks.isConditional(state.top())) { + throw new SyntaxError("An else if used outside of an if block"); + } + if (ControlFlowCommandChecks.isElse(state.top())) { + throw new SyntaxError("Incorrect command order of else if / else"); + } + state.push({ command: commandName }); +} + +function validateEnd (command, state) { + if (ControlFlowCommandChecks.isBlockOpen(state.top())) { + state.pop(); + } else if (ControlFlowCommandChecks.isElseOrElseIf(state.top())) { + state.pop(); + validateEnd(command, state); + } else { + throw new SyntaxError("Use of end without an opening keyword"); + } +} + +function validateRepeatIf (commandName, state) { + if (!ControlFlowCommandChecks.isDo(state.top())) { + throw new SyntaxError("A repeat if used without a do block"); + } + state.pop(); +} From 6a09008a1b3cea4d4eb9e143f421ac3fed4b6fa1 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 09:54:05 -0400 Subject: [PATCH 047/125] Added correct file extension to test file and added another test case (for piece of mind) --- .../{syntax-validation.js => syntax-validation.spec.js} | 7 +++++++ 1 file changed, 7 insertions(+) rename packages/selenium-ide/src/neo/__test__/playback/{syntax-validation.js => syntax-validation.spec.js} (96%) diff --git a/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js similarity index 96% rename from packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js rename to packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js index dd087f41a..ebfa76c38 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js +++ b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js @@ -123,6 +123,13 @@ describe("Control Flow", () => { let input = [ createCommand(ControlFlowCommandNames.else) ]; expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); }); + test("else, else", () => { + let input = [ + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.else) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); + }); test("elseIf", () => { let input = [ createCommand(ControlFlowCommandNames.elseIf) ]; expect(function() { validateControlFlowSyntax(input); }).toThrow("An else if used outside of an if block"); From cda8742c0e8144643b0837064ccfa917c1ce2840 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 09:54:05 -0400 Subject: [PATCH 048/125] Added correct file extension to test file and added another test case (for peace of mind) --- .../{syntax-validation.js => syntax-validation.spec.js} | 7 +++++++ 1 file changed, 7 insertions(+) rename packages/selenium-ide/src/neo/__test__/playback/{syntax-validation.js => syntax-validation.spec.js} (96%) diff --git a/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js similarity index 96% rename from packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js rename to packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js index dd087f41a..ebfa76c38 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.js +++ b/packages/selenium-ide/src/neo/__test__/playback/syntax-validation.spec.js @@ -123,6 +123,13 @@ describe("Control Flow", () => { let input = [ createCommand(ControlFlowCommandNames.else) ]; expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); }); + test("else, else", () => { + let input = [ + createCommand(ControlFlowCommandNames.else), + createCommand(ControlFlowCommandNames.else) + ]; + expect(function() { validateControlFlowSyntax(input); }).toThrow("An else used outside of an if block"); + }); test("elseIf", () => { let input = [ createCommand(ControlFlowCommandNames.elseIf) ]; expect(function() { validateControlFlowSyntax(input); }).toThrow("An else if used outside of an if block"); From c5f962f4edf505f518b17fd8124123e5be93bdfb Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 11 Jul 2018 14:47:03 -0400 Subject: [PATCH 049/125] Wired up control flow syntax validation and command indentation. To highlight the offending command I created a subclass of SyntaxError that includes an #index variable since SyntaxError's #lineNumber optional variable is non-standard and didn't work locally for me. --- .../src/neo/IO/SideeX/playback.js | 21 +++++++++-- .../src/neo/components/TestRow/index.jsx | 5 ++- .../src/neo/components/TestTable/index.jsx | 3 ++ .../control-flow-syntax-error.js | 23 ++++++++++++ .../playback-tree/syntax-validation.js | 37 ++++++++++--------- 5 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 packages/selenium-ide/src/neo/playback/playback-tree/control-flow-syntax-error.js diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 3936db9d3..d54997384 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -23,7 +23,7 @@ import UiState from "../../stores/view/UiState"; import { canExecuteCommand, executeCommand } from "../../../plugin/commandExecutor"; import ExtCommand, { isExtCommand } from "./ext-command"; import { xlateArgument } from "./formatCommand"; -//import { createPlaybackTree } from "../../playback/playback-tree"; +import { createPlaybackTree } from "../../playback/playback-tree"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -43,13 +43,21 @@ let ignoreBreakpoint = false; function play(currUrl) { baseUrl = currUrl; ignoreBreakpoint = false; - //playbackTree = createPlaybackTree(PlaybackState.runningQueue); + initPlaybackTree(); prepareToPlay() .then(executionLoop) .then(finishPlaying) .catch(catchPlayingError); } +function initPlaybackTree() { + try { + createPlaybackTree(PlaybackState.runningQueue); + } catch (error) { + reportError(error.message, false, error.index); + } +} + function playAfterConnectionFailed() { prepareToPlayAfterConnectionFailed() .then(executionLoop) @@ -142,8 +150,13 @@ function catchPlayingError(message) { } } -function reportError(error, nonFatal) { - const { id } = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex]; +function reportError(error, nonFatal, index) { + let id; + if (index) { + id = PlaybackState.runningQueue[index].id; + } else { + id = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].id; + } let message = error; if (error.message === "this.playingFrameLocations[this.currentPlayingTabId] is undefined") { message = "The current tab is invalid for testing (e.g. about:home), surf to a webpage before using the extension"; diff --git a/packages/selenium-ide/src/neo/components/TestRow/index.jsx b/packages/selenium-ide/src/neo/components/TestRow/index.jsx index cea94d624..c9b50ff06 100644 --- a/packages/selenium-ide/src/neo/components/TestRow/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestRow/index.jsx @@ -125,7 +125,8 @@ class TestRow extends React.Component { moveSelection: PropTypes.func, setSectionFocus: PropTypes.func, onContextMenu: PropTypes.func, - setContextMenu: PropTypes.func + setContextMenu: PropTypes.func, + level: PropTypes.number }; componentDidMount() { if (this.props.selected) { @@ -209,6 +210,7 @@ class TestRow extends React.Component { } } render() { + const commandIndentation = ; const listMenu = (!this.props.isPristine && !this.props.readOnly) ? }> Cut Copy @@ -255,6 +257,7 @@ class TestRow extends React.Component { {this.props.command.comment} + { commandIndentation } {this.props.command.command} {this.props.command.target} diff --git a/packages/selenium-ide/src/neo/components/TestTable/index.jsx b/packages/selenium-ide/src/neo/components/TestTable/index.jsx index 010fd9e92..44ec307c5 100644 --- a/packages/selenium-ide/src/neo/components/TestTable/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestTable/index.jsx @@ -25,6 +25,7 @@ import UiState from "../../stores/view/UiState"; import PlaybackState from "../../stores/view/PlaybackState"; import TestRow from "../TestRow"; import "./style.css"; +import { deriveCommandLevels } from "../../playback/playback-tree/command-leveler"; @observer export default class TestTable extends React.Component { @@ -60,6 +61,7 @@ export default class TestTable extends React.Component { } } render() { + const commandLevels = deriveCommandLevels(this.props.commands); const commandStatePrefix = this.props.callstackIndex !== undefined ? `${this.props.callstackIndex}:` : ""; return ([
@@ -97,6 +99,7 @@ export default class TestTable extends React.Component { pasteFromClipboard={UiState.pasteFromClipboard} clearAllCommands={this.props.clearAllCommands} setSectionFocus={UiState.setSectionFocus} + level={commandLevels[index]} /> )).concat( Date: Tue, 17 Jul 2018 06:44:18 -0400 Subject: [PATCH 050/125] Adjust linked list generation for control flow to skip over terminal keywords (e.g., else, do, and end). --- .../__test__/playback/playback-tree.spec.js | 67 +++++++++---------- .../selenium-ide/src/neo/models/Command.js | 9 ++- .../src/neo/playback/playback-tree/index.js | 44 ++++++------ 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index ab0aea5ea..f599a88e1 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -62,15 +62,15 @@ describe("Control Flow", () => { expect(stack[0].right).toEqual(stack[1]); expect(stack[0].left).toEqual(stack[2]); // command - expect(stack[1].next).toEqual(stack[6]); + expect(stack[1].next).toBeUndefined(); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); // elseIf expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toEqual(stack[3]); - expect(stack[2].left).toEqual(stack[4]); + expect(stack[2].left).toEqual(stack[5]); // command - expect(stack[3].next).toEqual(stack[6]); + expect(stack[3].next).toBeUndefined(); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // else @@ -78,7 +78,7 @@ describe("Control Flow", () => { expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // command - expect(stack[5].next).toEqual(stack[6]); + expect(stack[5].next).toBeUndefined(); expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); // end @@ -95,7 +95,7 @@ describe("Control Flow", () => { let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[2]); + expect(stack[0].left).toBeUndefined(); expect(stack[1].next).toEqual(stack[0]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); @@ -113,7 +113,7 @@ describe("Control Flow", () => { let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[3]); + expect(stack[0].left).toBeUndefined(); expect(stack[1].next).toEqual(stack[2]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); @@ -137,7 +137,7 @@ describe("Control Flow", () => { // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[5]); + expect(stack[0].left).toBeUndefined(); // command expect(stack[1].next).toEqual(stack[2]); expect(stack[1].right).toBeUndefined(); @@ -145,13 +145,13 @@ describe("Control Flow", () => { // while expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toEqual(stack[3]); - expect(stack[2].left).toEqual(stack[4]); + expect(stack[2].left).toBeUndefined(); // command expect(stack[3].next).toEqual(stack[2]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // end - expect(stack[4].next).toEqual(stack[5]); + expect(stack[4].next).toBeUndefined(); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // end @@ -174,29 +174,29 @@ describe("Control Flow", () => { // if expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[5]); + expect(stack[0].left).toEqual(stack[6]); // while expect(stack[1].next).toBeUndefined(); expect(stack[1].right).toEqual(stack[2]); - expect(stack[1].left).toEqual(stack[3]); + expect(stack[1].left).toEqual(stack[4]); // command expect(stack[2].next).toEqual(stack[1]); expect(stack[2].right).toBeUndefined(); expect(stack[2].left).toBeUndefined(); // end - expect(stack[3].next).toEqual(stack[4]); + expect(stack[3].next).toBeUndefined(); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // command - expect(stack[4].next).toEqual(stack[7]); + expect(stack[4].next).toBeUndefined(); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // else - expect(stack[5].next).toEqual(stack[6]); + expect(stack[5].next).toEqual(stack[6]); // TODO expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); // command - expect(stack[6].next).toEqual(stack[7]); + expect(stack[6].next).toBeUndefined(); expect(stack[6].right).toBeUndefined(); expect(stack[6].left).toBeUndefined(); // end @@ -204,7 +204,7 @@ describe("Control Flow", () => { expect(stack[7].right).toBeUndefined(); expect(stack[7].left).toBeUndefined(); }); - test("do-command-repeatIf-end", () => { + test("do-command-repeatIf-command", () => { let input = [ createCommand(ControlFlowCommandNames.do), createCommand("command"), @@ -219,13 +219,13 @@ describe("Control Flow", () => { expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); expect(stack[2].next).toBeUndefined(); - expect(stack[2].right).toEqual(stack[0]); + expect(stack[2].right).toEqual(stack[1]); expect(stack[2].left).toEqual(stack[3]); expect(stack[3].next).toBeUndefined(); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); }); - test("do-command-while-end-repeatIf", () => { + test("do-command-while-command-end-repeatIf", () => { let input = [ createCommand(ControlFlowCommandNames.do), createCommand("command"), @@ -243,27 +243,27 @@ describe("Control Flow", () => { expect(stack[1].left).toBeUndefined(); expect(stack[2].next).toBeUndefined(); expect(stack[2].right).toEqual(stack[3]); - expect(stack[2].left).toEqual(stack[4]); + expect(stack[2].left).toEqual(stack[5]); expect(stack[3].next).toEqual(stack[2]); expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); - expect(stack[4].next).toEqual(stack[5]); + expect(stack[4].next).toBeUndefined(); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); expect(stack[5].next).toBeUndefined(); - expect(stack[5].right).toBeUndefined(); + expect(stack[5].right).toEqual(stack[1]); expect(stack[5].left).toBeUndefined(); }); test("times-command-end", () => { let input = [ - createCommand("times"), + createCommand(ControlFlowCommandNames.times), createCommand("command"), createCommand(ControlFlowCommandNames.end) ]; let stack = createCommandNodesFromCommandStack(input); expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toEqual(stack[1]); - expect(stack[0].left).toEqual(stack[2]); + expect(stack[0].left).toBeUndefined(); expect(stack[1].next).toEqual(stack[0]); expect(stack[1].right).toBeUndefined(); expect(stack[1].left).toBeUndefined(); @@ -291,17 +291,16 @@ describe("Control Flow", () => { createCommand(ControlFlowCommandNames.end) ]; let tree = createPlaybackTree(input); - expect(tree.currentCommandNode.command).toEqual(input[0]); // if - expect(tree.currentCommandNode.right.command).toEqual(input[1]); // if -> command - expect(tree.currentCommandNode.right.next.command).toEqual(input[12]); // if command -> end - expect(tree.currentCommandNode.left.command).toEqual(input[2]); // if -> else - expect(tree.currentCommandNode.left.next.right.command).toEqual(input[4]); // while -> command - expect(tree.currentCommandNode.left.next.left.command).toEqual(input[5]); // while -> end - expect(tree.currentCommandNode.left.next.left.next.next.command).toEqual(input[7]); // do -> command - expect(tree.currentCommandNode.left.next.left.next.next.next.right.command).toEqual(input[9]); // while -> command - expect(tree.currentCommandNode.left.next.left.next.next.next.left.command).toEqual(input[10]); // while -> end - expect(tree.currentCommandNode.left.next.left.next.next.next.left.next.right.command).toEqual(input[6]); // repeatIf -> do - expect(tree.currentCommandNode.left.next.left.next.next.next.left.next.left.command).toEqual(input[12]); // repeatIf -> end + expect(tree.currentCommandNode.command).toEqual(input[0]); // if + expect(tree.currentCommandNode.right.command).toEqual(input[1]); // if -> command + expect(tree.currentCommandNode.right.next).toBeUndefined(); // if command -> undefined (end + 1) + expect(tree.currentCommandNode.left.command).toEqual(input[3]); // if -> while (else + 1) + expect(tree.currentCommandNode.left.right.command).toEqual(input[4]); // while -> command + expect(tree.currentCommandNode.left.left.command).toEqual(input[7]); // while -> command (end + 1, do + 1) + expect(tree.currentCommandNode.left.left.next.right.command).toEqual(input[9]); // do -> while -> command + expect(tree.currentCommandNode.left.left.next.left.command).toEqual(input[11]); // do -> while -> repeatIf (end + 1) + expect(tree.currentCommandNode.left.left.next.left.right.command).toEqual(input[7]); // repeatIf -> do command + expect(tree.currentCommandNode.left.left.next.left.left).toBeUndefined(); // repeatIf -> undefined (end + 1) }); }); }); diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 3806fb87d..796aa3649 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -845,6 +845,12 @@ function isBlockOpen(command) { return (isIf(command) || isLoop(command)); } +function isTerminal(command) { + return (isElse(command) || + isDo(command) || + isEnd(command)); +} + export const ControlFlowCommandChecks = { isConditional: isConditional, isDo: isDo, @@ -853,6 +859,7 @@ export const ControlFlowCommandChecks = { isEnd: isEnd, isIf: isIf, isLoop: isLoop, - isBlockOpen: isBlockOpen + isBlockOpen: isBlockOpen, + isTerminal: isTerminal }; diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index fff5f0160..aadee20cc 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -52,12 +52,10 @@ function connectCommandNodes(_commandNodeStack) { let state = new State; commandNodeStack.forEach(function(commandNode) { let nextCommandNode = commandNodeStack[commandNode.index + 1]; - if (nextCommandNode) { - if (connectCommandNode[commandNode.command.command]) { - connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, commandNodeStack, state); - } else { - connectDefault(commandNode, nextCommandNode, commandNodeStack, state); - } + if (connectCommandNode[commandNode.command.command]) { + connectCommandNode[commandNode.command.command](commandNode, nextCommandNode, commandNodeStack, state); + } else { + connectDefault(commandNode, nextCommandNode, commandNodeStack, state); } }); return commandNodeStack; @@ -77,11 +75,12 @@ let connectCommandNode = { function connectDefault (commandNode, _nextCommandNode, stack, state) { let nextCommandNode; if (ControlFlowCommandChecks.isIf(state.top()) && (ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command))) { - nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); + let next = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); + nextCommandNode = deriveNext(next, stack); } else if (ControlFlowCommandChecks.isLoop(state.top()) && ControlFlowCommandChecks.isEnd(_nextCommandNode.command)) { nextCommandNode = stack[state.top().index]; } else { - nextCommandNode = _nextCommandNode; + nextCommandNode = deriveNext(_nextCommandNode, stack); } connectNext(commandNode, nextCommandNode); } @@ -92,8 +91,9 @@ function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, s } function connectConditional (commandNode, nextCommandNode, stack) { + let left = findNextNodeBy(stack, commandNode.index, commandNode.level); commandNode.right = nextCommandNode; - commandNode.left = findNextNodeBy(stack, commandNode.index, commandNode.level); + commandNode.left = deriveNext(left, stack); } function connectNextForBranchOpen (commandNode, nextCommandNode, stack, state) { @@ -106,22 +106,13 @@ function connectNext (commandNode, nextCommandNode) { } function connectRepeatIf (commandNode, nextCommandNode, stack, state) { - commandNode.right = stack[state.top().index]; - commandNode.left = nextCommandNode; + commandNode.right = stack[state.top().index + 1]; + commandNode.left = deriveNext(nextCommandNode, stack); state.pop(); } function connectEnd (commandNode, _nextCommandNode, stack, state) { state.pop(); - if (!state.empty()) { - let nextCommandNode; - if (ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command)) { - nextCommandNode = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); - } else { - nextCommandNode = _nextCommandNode; - } - connectNext(commandNode, nextCommandNode); - } } function findNextNodeBy(stack, index, level, commandName) { @@ -138,3 +129,16 @@ function findNextNodeBy(stack, index, level, commandName) { } } } + +function deriveNext (nextCommandNode, stack) { + if (nextCommandNode && ControlFlowCommandChecks.isTerminal(nextCommandNode.command)) { + let result = stack[nextCommandNode.index + 1]; + if (result && ControlFlowCommandChecks.isTerminal(result.command)) { + return deriveNext(result, stack); + } else { + return result; + } + } else { + return nextCommandNode; + } +} From c44d833fdaa4b1f100210054ea32f4596fd133d3 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 17 Jul 2018 07:10:49 -0400 Subject: [PATCH 051/125] Cleaned up end, else, and do command node linking a bit further. --- .../__test__/playback/playback-tree.spec.js | 8 ++-- .../src/neo/playback/playback-tree/index.js | 42 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index f599a88e1..537231505 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -74,7 +74,7 @@ describe("Control Flow", () => { expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toBeUndefined(); // else - expect(stack[4].next).toEqual(stack[5]); + expect(stack[4].next).toBeUndefined(); expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // command @@ -192,7 +192,7 @@ describe("Control Flow", () => { expect(stack[4].right).toBeUndefined(); expect(stack[4].left).toBeUndefined(); // else - expect(stack[5].next).toEqual(stack[6]); // TODO + expect(stack[5].next).toBeUndefined(); expect(stack[5].right).toBeUndefined(); expect(stack[5].left).toBeUndefined(); // command @@ -212,7 +212,7 @@ describe("Control Flow", () => { createCommand("command") ]; let stack = createCommandNodesFromCommandStack(input); - expect(stack[0].next).toEqual(stack[1]); + expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); expect(stack[1].next).toEqual(stack[2]); @@ -235,7 +235,7 @@ describe("Control Flow", () => { createCommand(ControlFlowCommandNames.repeatIf) ]; let stack = createCommandNodesFromCommandStack(input); - expect(stack[0].next).toEqual(stack[1]); + expect(stack[0].next).toBeUndefined(); expect(stack[0].right).toBeUndefined(); expect(stack[0].left).toBeUndefined(); expect(stack[1].next).toEqual(stack[2]); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index aadee20cc..6e98c0e15 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -62,31 +62,40 @@ function connectCommandNodes(_commandNodeStack) { } let connectCommandNode = { - [ControlFlowCommandNames.do]: connectNextForBranchOpen, - [ControlFlowCommandNames.else]: connectNext, + [ControlFlowCommandNames.do]: trackBranchOpen, [ControlFlowCommandNames.elseIf]: connectConditional, - [ControlFlowCommandNames.end]: connectEnd, + [ControlFlowCommandNames.end]: trackBranchClose, [ControlFlowCommandNames.if]: connectConditionalForBranchOpen, - [ControlFlowCommandNames.repeatIf]: connectRepeatIf, + [ControlFlowCommandNames.repeatIf]: connectDo, [ControlFlowCommandNames.times]: connectConditionalForBranchOpen, [ControlFlowCommandNames.while]: connectConditionalForBranchOpen }; function connectDefault (commandNode, _nextCommandNode, stack, state) { let nextCommandNode; - if (ControlFlowCommandChecks.isIf(state.top()) && (ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command))) { + if (ControlFlowCommandChecks.isIf(state.top()) && ControlFlowCommandChecks.isElseOrElseIf(_nextCommandNode.command)) { let next = findNextNodeBy(stack, commandNode.index, state.top().level, ControlFlowCommandNames.end); nextCommandNode = deriveNext(next, stack); } else if (ControlFlowCommandChecks.isLoop(state.top()) && ControlFlowCommandChecks.isEnd(_nextCommandNode.command)) { nextCommandNode = stack[state.top().index]; + } else if (ControlFlowCommandChecks.isElse(commandNode.command)) { + nextCommandNode = undefined; } else { nextCommandNode = deriveNext(_nextCommandNode, stack); } connectNext(commandNode, nextCommandNode); } -function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, state) { +function trackBranchOpen (commandNode, nextCommandNode, stack, state) { state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); +} + +function trackBranchClose (commandNode, nextCommandNode, stack, state) { + state.pop(); +} + +function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, state) { + trackBranchOpen(commandNode, nextCommandNode, stack, state); connectConditional(commandNode, nextCommandNode, stack); } @@ -96,23 +105,14 @@ function connectConditional (commandNode, nextCommandNode, stack) { commandNode.left = deriveNext(left, stack); } -function connectNextForBranchOpen (commandNode, nextCommandNode, stack, state) { - state.push({ command: commandNode.command.command, level: commandNode.level, index: commandNode.index }); - connectNext(commandNode, nextCommandNode); -} - function connectNext (commandNode, nextCommandNode) { commandNode.next = nextCommandNode; } -function connectRepeatIf (commandNode, nextCommandNode, stack, state) { +function connectDo (commandNode, nextCommandNode, stack, state) { commandNode.right = stack[state.top().index + 1]; commandNode.left = deriveNext(nextCommandNode, stack); - state.pop(); -} - -function connectEnd (commandNode, _nextCommandNode, stack, state) { - state.pop(); + trackBranchClose(commandNode, nextCommandNode, stack, state); } function findNextNodeBy(stack, index, level, commandName) { @@ -130,15 +130,15 @@ function findNextNodeBy(stack, index, level, commandName) { } } -function deriveNext (nextCommandNode, stack) { - if (nextCommandNode && ControlFlowCommandChecks.isTerminal(nextCommandNode.command)) { - let result = stack[nextCommandNode.index + 1]; +function deriveNext (targetNode, stack) { + if (targetNode && ControlFlowCommandChecks.isTerminal(targetNode.command)) { + let result = stack[targetNode.index + 1]; if (result && ControlFlowCommandChecks.isTerminal(result.command)) { return deriveNext(result, stack); } else { return result; } } else { - return nextCommandNode; + return targetNode; } } From 5a89c8d7fe9640fc6ed9e61af56b0549802486ee Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 17 Jul 2018 07:37:15 -0400 Subject: [PATCH 052/125] Fixed malformed copyright header --- .../src/neo/playback/playback-tree/index.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 6e98c0e15..709906c64 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -1,18 +1,18 @@ // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file -// dControlFlowCommandChecks.istributed with thControlFlowCommandChecks.is work for additional information -// regarding copyright ownership. The SFC licenses thControlFlowCommandChecks.is 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 thControlFlowCommandChecks.is file except in compliance +// "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 dControlFlowCommandChecks.istributed under the License ControlFlowCommandChecks.is dControlFlowCommandChecks.istributed on an -// "AS ControlFlowCommandChecks.is" BASControlFlowCommandChecks.is, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// 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 permControlFlowCommandChecks.issions and limitations +// specific language governing permissions and limitations // under the License. import { CommandNode } from "./command-node"; From 29a54e6966b148618a23336fb1286b602597241d Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 17 Jul 2018 07:51:07 -0400 Subject: [PATCH 053/125] Added test to check end skip recursion. --- .../__test__/playback/playback-tree.spec.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index 537231505..ed1a178c1 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -271,6 +271,47 @@ describe("Control Flow", () => { expect(stack[2].right).toBeUndefined(); expect(stack[2].left).toBeUndefined(); }); + test("if-if-if-if-end-end-end-command-end", () => { + let input = [ + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.if), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.end), + createCommand(ControlFlowCommandNames.end), + createCommand("command"), + createCommand(ControlFlowCommandNames.end) + ]; + let stack = createCommandNodesFromCommandStack(input); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toEqual(stack[1]); + expect(stack[0].left).toBeUndefined(); + expect(stack[1].next).toBeUndefined(); + expect(stack[1].right).toEqual(stack[2]); + expect(stack[1].left).toEqual(stack[7]); + expect(stack[2].next).toBeUndefined(); + expect(stack[2].right).toEqual(stack[3]); + expect(stack[2].left).toEqual(stack[7]); + expect(stack[3].next).toBeUndefined(); + expect(stack[3].right).toEqual(stack[4]); + expect(stack[3].left).toEqual(stack[7]); + expect(stack[4].next).toBeUndefined(); + expect(stack[4].right).toBeUndefined(); + expect(stack[4].left).toBeUndefined(); + expect(stack[5].next).toBeUndefined(); + expect(stack[5].right).toBeUndefined(); + expect(stack[5].left).toBeUndefined(); + expect(stack[6].next).toBeUndefined(); + expect(stack[6].right).toBeUndefined(); + expect(stack[6].left).toBeUndefined(); + expect(stack[7].next).toBeUndefined(); + expect(stack[7].right).toBeUndefined(); + expect(stack[7].left).toBeUndefined(); + expect(stack[8].next).toBeUndefined(); + expect(stack[8].right).toBeUndefined(); + expect(stack[8].left).toBeUndefined(); + }); }); }); describe("Processed", () => { From ff55c7825e510ec022e4e24a1805dce34250db6b Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 17 Jul 2018 15:09:49 -0400 Subject: [PATCH 054/125] A rough first pass on getting playback wired up with playback-tree. --- .../src/neo/IO/SideeX/playback.js | 57 +++++++------------ .../playback/playback-tree/command-node.js | 51 ++++++++++++----- .../src/neo/stores/view/PlaybackState.js | 15 +++-- 3 files changed, 68 insertions(+), 55 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index d54997384..1026528a7 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -38,7 +38,7 @@ extCommand.doSetSpeed = (speed) => { let baseUrl = ""; let ignoreBreakpoint = false; -//let playbackTree; +let playbackTree; function play(currUrl) { baseUrl = currUrl; @@ -52,7 +52,8 @@ function play(currUrl) { function initPlaybackTree() { try { - createPlaybackTree(PlaybackState.runningQueue); + playbackTree = createPlaybackTree(PlaybackState.runningQueue); + PlaybackState.setCurrentExecutingCommandNode(playbackTree.currentCommandNode); } catch (error) { reportError(error.message, false, error.index); } @@ -66,34 +67,18 @@ function playAfterConnectionFailed() { } function didFinishQueue() { - return (PlaybackState.currentPlayingIndex >= PlaybackState.runningQueue.length && PlaybackState.isPlaying); + return !PlaybackState.currentExecutingCommandNode; } function isStopping() { return (!PlaybackState.isPlaying || PlaybackState.paused || PlaybackState.isStopping); } -function incrementPlayingIndex() { - if (PlaybackState.currentPlayingIndex < 0) { - PlaybackState.setPlayingIndex(0); - } else { - PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex + 1); - } -} - -function isCallStackEmpty() { - return !PlaybackState.callstack.length; -} - function executionLoop() { - incrementPlayingIndex(); - if (didFinishQueue() && !isCallStackEmpty()) { - PlaybackState.unwindTestCase(); - return executionLoop(); - } else if (isStopping() || didFinishQueue()) { + if (isStopping() || didFinishQueue()) { return false; } - const command = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex]; + const command = PlaybackState.currentExecutingCommandNode.command; const stackIndex = PlaybackState.callstack.length ? PlaybackState.callstack.length - 1 : undefined; if (!command.command) return executionLoop(); // breakpoint @@ -103,13 +88,13 @@ function executionLoop() { // paused if (isStopping()) return false; if (isExtCommand(command.command)) { - return doDelay().then(() => ( - (extCommand[extCommand.name(command.command)](xlateArgument(command.target), xlateArgument(command.value))) - .then(() => { - // we need to set the stackIndex manually because run command messes with that - PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); - }).then(executionLoop) - )); + return doDelay().then(() => { + return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { + // we need to set the stackIndex manually because run command messes with that + PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); + PlaybackState.setCurrentExecutingCommandNode(result.next); + }).then(executionLoop); + }); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); return executionLoop(); @@ -126,7 +111,6 @@ function executionLoop() { } function prepareToPlay() { - PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex - 1); return extCommand.init(baseUrl); } @@ -141,7 +125,6 @@ function finishPlaying() { function catchPlayingError(message) { if (isReceivingEndError(message)) { setTimeout(function() { - PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex - 1); playAfterConnectionFailed(); }, 100); } else { @@ -155,7 +138,11 @@ function reportError(error, nonFatal, index) { if (index) { id = PlaybackState.runningQueue[index].id; } else { - id = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex].id; + if (PlaybackState.currentlyExecutingCommandNode) { + id = PlaybackState.currentlyExecutingCommandNode.command.id; + } else { + console.log(error); + } } let message = error; if (error.message === "this.playingFrameLocations[this.currentPlayingTabId] is undefined") { @@ -177,7 +164,6 @@ reaction( () => PlaybackState.paused, paused => { if (!paused) { - PlaybackState.setPlayingIndex(PlaybackState.currentPlayingIndex - 1); ignoreBreakpoint = true; playAfterConnectionFailed(); } @@ -258,7 +244,7 @@ function doDomWait(res, domTime, domCount = 0) { function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { if (!PlaybackState.isPlaying || PlaybackState.paused) return; - const { id, command, target, value } = PlaybackState.runningQueue[PlaybackState.currentPlayingIndex]; + const { id, command, target, value } = PlaybackState.currentExecutingCommandNode.command; let p = new Promise(function(resolve, reject) { let count = 0; @@ -286,7 +272,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { function doSeleniumCommand(id, command, target, value, implicitTime, implicitCount) { return (command !== "type" - ? extCommand.sendMessage(command, xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command)) + ? PlaybackState.currentExecutingCommandNode.execute(extCommand) : extCommand.doType(xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command))).then(function(result) { if (result.result !== "success") { // implicit @@ -296,6 +282,7 @@ function doSeleniumCommand(id, command, target, value, implicitTime, implicitCou PlaybackState.setCommandState(id, /^verify/.test(command) ? PlaybackStates.Failed : PlaybackStates.Fatal, result.result); } } else { + PlaybackState.setCurrentExecutingCommandNode(result.next); PlaybackState.setCommandState(id, PlaybackStates.Passed); } }); @@ -346,7 +333,7 @@ function doImplicitWait(error, commandId, target, implicitTime, implicitCount) { function doDelay() { return new Promise((res) => { - if (PlaybackState.currentPlayingIndex + 1 === PlaybackState.runningQueue.length) { + if (PlaybackState.currentPlayingIndex + 1 === PlaybackState.runningQueue.length) { // TODO return res(true); } else { setTimeout(() => { diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 96153242e..d24173cf7 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -15,7 +15,8 @@ // specific language governing permissions and limitations // under the License. -import { ControlFlowCommandChecks } from "../../models/Command"; +import { isExtCommand } from "../../IO/SideeX/ext-command"; +import { xlateArgument } from "../../IO/SideeX/formatCommand"; export class CommandNode { constructor(command) { @@ -28,23 +29,43 @@ export class CommandNode { this.timesVisited = 0; } - isConditional() { - !!(this.left && this.right); + isControlFlow() { + !!(this.left || this.right); } - evaluate(playbackTree, extCommand) { - if (this.isConditional()) { - if (extCommand.evaluateConditional(this.command.target)) { - playbackTree.currentCommandNode = this.right; - } else { - playbackTree.currentCommandNode = this.left; - } + execute(extCommand) { + if (isExtCommand(this.command.command)) { + return extCommand[extCommand.name(this.command.command)](xlateArgument(this.command.target), xlateArgument(this.command.value)).then(() => { + return { + next: this.next + }; + }); + } else if (this.isControlFlow()) { + return this.evaluate(); } else { - if (ControlFlowCommandChecks.isEnd(this.command)) { - playbackTree.currentCommandNode = this.next; - } else { - extCommand.sendMessage(this.command, this.command.target, this.command.value, false); - } + return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false).then((result) => { + if (result.result === "success") { + return { + result: "success", + next: this.next + }; + } else { + return result; + } + }); + } + } + + evaluate() { + let v = true; + if (v) { + return { + next: this.right + }; + } else { + return { + next: this.left + }; } } } diff --git a/packages/selenium-ide/src/neo/stores/view/PlaybackState.js b/packages/selenium-ide/src/neo/stores/view/PlaybackState.js index 383d71f2a..8322382a1 100644 --- a/packages/selenium-ide/src/neo/stores/view/PlaybackState.js +++ b/packages/selenium-ide/src/neo/stores/view/PlaybackState.js @@ -48,6 +48,7 @@ class PlaybackState { @observable paused = false; @observable delay = 0; @observable callstack = []; + @observable currentExecutingCommandNode = null; constructor() { this.maxDelay = 3000; @@ -196,7 +197,7 @@ class PlaybackState { this.currentRunningTest = this._testsToRun.shift(); this.runningQueue = this.currentRunningTest.commands.peek(); this.clearStack(); - this.currentPlayingIndex = 0; + //this.currentPlayingIndex = 0; this.errors = 0; PluginManager.emitMessage({ action: "event", @@ -277,9 +278,9 @@ class PlaybackState { this.aborted = true; this._testsToRun = []; if (this.paused) { - this.setCommandStateAtomically(this.runningQueue[this.currentPlayingIndex].id, this.callstack.length ? this.callstack.length - 1 : undefined, PlaybackStates.Fatal, "Playback aborted"); + this.setCommandStateAtomically(this.currentExecutingCommandNode.command.id, this.callstack.length ? this.callstack.length - 1 : undefined, PlaybackStates.Fatal, "Playback aborted"); } else { - this.setCommandStateAtomically(this.runningQueue[this.currentPlayingIndex].id, this.callstack.length ? this.callstack.length - 1 : undefined, PlaybackStates.Undetermined, "Aborting..."); + this.setCommandStateAtomically(this.currentExecutingCommandNode.command.id, this.callstack.length ? this.callstack.length - 1 : undefined, PlaybackStates.Undetermined, "Aborting..."); } this.stopPlayingGracefully(); } @@ -333,6 +334,10 @@ class PlaybackState { }); } + @action.bound setCurrentExecutingCommandNode(node) { + this.currentExecutingCommandNode = node; + } + @action.bound setPlayingIndex(index) { this.currentPlayingIndex = index; } @@ -351,8 +356,8 @@ class PlaybackState { if (!this.pauseOnExceptions) { this.stopPlayingGracefully(); } else if (this.pauseOnExceptions) { - this.break(this.runningQueue[this.currentPlayingIndex]); - this.setPlayingIndex(this.currentPlayingIndex - 1); + this.break(this.currentExecutingCommandNode.command); + //this.setPlayingIndex(this.currentPlayingIndex - 1); } } } From 4b70d8b9fccbaf9d288cb8afd044b7be571284c6 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 18 Jul 2018 07:34:32 -0400 Subject: [PATCH 055/125] Added execution for control flow commands to playback. Pulled the setting of `currentExecutingCommandNode` up into `CommanNode#execute`. Also added an `isExtCommand` method to `extCommand` which uses the `isExtCommand` exported function. This avoids the need for a seemingly unnecessary import in `CommandNode` and a constructor `TypeError` in the playback-tree tests. --- .../src/neo/IO/SideeX/ext-command.js | 4 ++++ .../selenium-ide/src/neo/IO/SideeX/playback.js | 18 ++++++++++-------- .../neo/playback/playback-tree/command-node.js | 17 +++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js index c01758a98..c6c1c1bbe 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js @@ -370,6 +370,10 @@ export default class ExtCommand { let upperCase = command.charAt(0).toUpperCase() + command.slice(1); return "do" + upperCase; } + + isExtCommand(command) { + return isExtCommand(command); + } } export function isExtCommand(command) { diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 1026528a7..d8001f5e7 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -87,13 +87,16 @@ function executionLoop() { else if (ignoreBreakpoint) ignoreBreakpoint = false; // paused if (isStopping()) return false; - if (isExtCommand(command.command)) { + if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { + PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode); + return executionLoop(); + } else if (isExtCommand(command.command)) { return doDelay().then(() => { - return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { - // we need to set the stackIndex manually because run command messes with that - PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); - PlaybackState.setCurrentExecutingCommandNode(result.next); - }).then(executionLoop); + return (PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode)) + .then(() => { + // we need to set the stackIndex manually because run command messes with that + PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); + }).then(executionLoop); }); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); @@ -272,7 +275,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { function doSeleniumCommand(id, command, target, value, implicitTime, implicitCount) { return (command !== "type" - ? PlaybackState.currentExecutingCommandNode.execute(extCommand) + ? PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode) : extCommand.doType(xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command))).then(function(result) { if (result.result !== "success") { // implicit @@ -282,7 +285,6 @@ function doSeleniumCommand(id, command, target, value, implicitTime, implicitCou PlaybackState.setCommandState(id, /^verify/.test(command) ? PlaybackStates.Failed : PlaybackStates.Fatal, result.result); } } else { - PlaybackState.setCurrentExecutingCommandNode(result.next); PlaybackState.setCommandState(id, PlaybackStates.Passed); } }); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index d24173cf7..35b64b575 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -15,7 +15,6 @@ // specific language governing permissions and limitations // under the License. -import { isExtCommand } from "../../IO/SideeX/ext-command"; import { xlateArgument } from "../../IO/SideeX/formatCommand"; export class CommandNode { @@ -30,24 +29,22 @@ export class CommandNode { } isControlFlow() { - !!(this.left || this.right); + return !!(this.left || this.right); } - execute(extCommand) { - if (isExtCommand(this.command.command)) { + execute(extCommand, setNextNode) { + if (extCommand.isExtCommand(this.command.command)) { return extCommand[extCommand.name(this.command.command)](xlateArgument(this.command.target), xlateArgument(this.command.value)).then(() => { - return { - next: this.next - }; + setNextNode(this.next); }); } else if (this.isControlFlow()) { - return this.evaluate(); + setNextNode(this.evaluate().next); } else { return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false).then((result) => { if (result.result === "success") { + setNextNode(this.next); return { - result: "success", - next: this.next + result: "success" }; } else { return result; From 005b1e793baec1045dbf62c039b3d94f44f8ca9e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 18 Jul 2018 11:39:48 -0400 Subject: [PATCH 056/125] Fixed linter warnings --- packages/selenium-ide/src/content/commands-api.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index acbd37a60..6948d8378 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -47,27 +47,27 @@ function doCommands(request, sender, sendResponse) { if (selenium["do" + upperCase] != null) { try { document.body.setAttribute("SideeXPlayingFlag", true); - let returnValue = selenium["do"+upperCase](selenium.preprocessParameter(request.target),selenium.preprocessParameter(request.value)); + let returnValue = selenium["do" + upperCase](selenium.preprocessParameter(request.target), selenium.preprocessParameter(request.value)); if (returnValue instanceof Promise) { // The command is a asynchronous function returnValue.then(function() { // Asynchronous command completed successfully document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: "success"}); + sendResponse({ result: "success" }); }).catch(function(reason) { // Asynchronous command failed document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: reason}); + sendResponse({ result: reason }); }); } else { // Synchronous command completed successfully document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: "success"}); + sendResponse({ result: "success" }); } } catch(e) { // Synchronous command failed document.body.removeAttribute("SideeXPlayingFlag"); - sendResponse({result: e.message}); + sendResponse({ result: e.message }); } } else { sendResponse({ result: "Unknown command: " + request.commands }); @@ -84,9 +84,9 @@ function doCommands(request, sender, sendResponse) { try { const element = selenium.browserbot.findElement(request.locator); const locator = locatorBuilders.buildAll(element).find(([loc, strat]) => (/^xpath/.test(strat)))[0]; //eslint-disable-line no-unused-vars - sendResponse({result: "success", locator}); + sendResponse({ result: "success", locator }); } catch(e) { - sendResponse({result: e.message}); + sendResponse({ result: e.message }); } } if (request.selectMode) { From 972c3b510844c52bb90fc4da7c81c531c08e3dec Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 18 Jul 2018 14:47:56 -0400 Subject: [PATCH 057/125] Wired up eval for expressions. Made a helper command for evaluateConditional that uses it. Also added some conditional steps to an existing seed test. Will revisit and clean up seeds later. --- .../selenium-ide/src/content/commands-api.js | 7 ++++ .../selenium-ide/src/content/selenium-api.js | 12 +++++- .../src/neo/IO/SideeX/playback.js | 13 ++++--- .../playback/playback-tree/command-node.js | 39 +++++++++++-------- packages/selenium-ide/src/neo/stores/seed.js | 9 +++++ 5 files changed, 57 insertions(+), 23 deletions(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 6948d8378..3ba5cf476 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -42,6 +42,13 @@ function doCommands(request, sender, sendResponse) { } else if (request.commands == "domWait") { selenium["doDomWait"]("", selenium.preprocessParameter("")); sendResponse({ dom_time: window.sideex_new_page }); + } else if (request.commands === "evaluateConditional") { + try { + let value = selenium["doEvaluateConditional"](request.target); + sendResponse({ result: "success", value }); + } catch(e) { + sendResponse({ result: e.message }); + } } else { const upperCase = request.commands.charAt(0).toUpperCase() + request.commands.slice(1); if (selenium["do" + upperCase] != null) { diff --git a/packages/selenium-ide/src/content/selenium-api.js b/packages/selenium-ide/src/content/selenium-api.js index 833081d17..3be512cb9 100644 --- a/packages/selenium-ide/src/content/selenium-api.js +++ b/packages/selenium-ide/src/content/selenium-api.js @@ -347,8 +347,16 @@ Selenium.prototype.reset = function() { this.browserbot.resetPopups(); }; -Selenium.prototype.eval = function(script, scoped = true) { - return window.eval(scoped ? `(() => {${script}})()` : script); +Selenium.prototype.eval = function(script, scoped = true, isExpression = false) { + if (isExpression) { + return window.eval(scoped ? `(() => (${script}))()` : script); + } else { + return window.eval(scoped ? `(() => {${script}})()` : script); + } +}; + +Selenium.prototype.doEvaluateConditional = function(condition) { + return !!(this.eval(condition, undefined, true)); }; Selenium.prototype.doVerifyChecked = function(locator) { diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index d8001f5e7..66a6f5040 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -88,14 +88,16 @@ function executionLoop() { // paused if (isStopping()) return false; if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { - PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode); - return executionLoop(); + return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { + PlaybackState.setCurrentExecutingCommandNode(result.next); + }).then(executionLoop); } else if (isExtCommand(command.command)) { return doDelay().then(() => { - return (PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode)) - .then(() => { + return (PlaybackState.currentExecutingCommandNode.execute(extCommand)) + .then((result) => { // we need to set the stackIndex manually because run command messes with that PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); + PlaybackState.setCurrentExecutingCommandNode(result.next); }).then(executionLoop); }); } else if (isImplicitWait(command)) { @@ -275,7 +277,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { function doSeleniumCommand(id, command, target, value, implicitTime, implicitCount) { return (command !== "type" - ? PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.setCurrentExecutingCommandNode) + ? PlaybackState.currentExecutingCommandNode.execute(extCommand) : extCommand.doType(xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command))).then(function(result) { if (result.result !== "success") { // implicit @@ -286,6 +288,7 @@ function doSeleniumCommand(id, command, target, value, implicitTime, implicitCou } } else { PlaybackState.setCommandState(id, PlaybackStates.Passed); + PlaybackState.setCurrentExecutingCommandNode(result.next); } }); } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 35b64b575..e04631152 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -32,19 +32,25 @@ export class CommandNode { return !!(this.left || this.right); } - execute(extCommand, setNextNode) { + execute(extCommand) { if (extCommand.isExtCommand(this.command.command)) { return extCommand[extCommand.name(this.command.command)](xlateArgument(this.command.target), xlateArgument(this.command.value)).then(() => { - setNextNode(this.next); + return { + next: this.next + }; }); } else if (this.isControlFlow()) { - setNextNode(this.evaluate().next); + return this.evaluate(extCommand).then((result) => { + return { + next: result.next + }; + }); } else { return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false).then((result) => { if (result.result === "success") { - setNextNode(this.next); return { - result: "success" + result: "success", + next: this.next }; } else { return result; @@ -53,16 +59,17 @@ export class CommandNode { } } - evaluate() { - let v = true; - if (v) { - return { - next: this.right - }; - } else { - return { - next: this.left - }; - } + evaluate(extCommand) { + return extCommand.sendMessage("evaluateConditional", this.command.target, "", false).then((result) => { + if (result.value) { + return { + next: this.right + }; + } else { + return { + next: this.left + }; + } + }); } } diff --git a/packages/selenium-ide/src/neo/stores/seed.js b/packages/selenium-ide/src/neo/stores/seed.js index 9059055d5..6b8ec1457 100644 --- a/packages/selenium-ide/src/neo/stores/seed.js +++ b/packages/selenium-ide/src/neo/stores/seed.js @@ -66,6 +66,15 @@ export default function seed(store, numberOfSuites = 5) { const secondClick = playbackTest.createCommand(); secondClick.setCommand("clickAt"); secondClick.setTarget("link=parliamentary systems"); + const ifCommand = playbackTest.createCommand(); + ifCommand.setCommand("if"); + ifCommand.setTarget("true"); + playbackTest.createCommand(undefined, "echo", "foo"); + playbackTest.createCommand().setCommand("else"); + const leftCommand = playbackTest.createCommand(); + leftCommand.setCommand("echo"); + leftCommand.setTarget("bar"); + playbackTest.createCommand().setCommand("end"); const playbackTest2 = store.createTestCase("aab playback"); const open2 = playbackTest2.createCommand(); From d2e5af6a387b46907e188d1d98d3ba522cf9758b Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 18 Jul 2018 14:51:10 -0400 Subject: [PATCH 058/125] Rolled the isExtCommand function into the class method of the same name and updated where it's used. --- .../src/neo/IO/SideeX/ext-command.js | 30 ++++++++----------- .../src/neo/IO/SideeX/playback.js | 4 +-- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js index c6c1c1bbe..bd881b1cd 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js @@ -372,22 +372,18 @@ export default class ExtCommand { } isExtCommand(command) { - return isExtCommand(command); - } -} - -export function isExtCommand(command) { - switch(command) { - case "pause": - case "open": - case "selectFrame": - case "selectWindow": - case "run": - case "setSpeed": - case "store": - case "close": - return true; - default: - return false; + switch(command) { + case "pause": + case "open": + case "selectFrame": + case "selectWindow": + case "run": + case "setSpeed": + case "store": + case "close": + return true; + default: + return false; + } } } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 66a6f5040..10dc98560 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -21,7 +21,7 @@ import NoResponseError from "../../../errors/no-response"; import PlaybackState, { PlaybackStates } from "../../stores/view/PlaybackState"; import UiState from "../../stores/view/UiState"; import { canExecuteCommand, executeCommand } from "../../../plugin/commandExecutor"; -import ExtCommand, { isExtCommand } from "./ext-command"; +import ExtCommand from "./ext-command"; import { xlateArgument } from "./formatCommand"; import { createPlaybackTree } from "../../playback/playback-tree"; @@ -91,7 +91,7 @@ function executionLoop() { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { PlaybackState.setCurrentExecutingCommandNode(result.next); }).then(executionLoop); - } else if (isExtCommand(command.command)) { + } else if (extCommand.isExtCommand(command.command)) { return doDelay().then(() => { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)) .then((result) => { From d6213c4f34e55576dce2f630ce4cfe6a10d2c321 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 18 Jul 2018 14:56:42 -0400 Subject: [PATCH 059/125] Moved isWindowMethodCommand into extCommand and updated where it's used. Also updated Selenium command execution in CommandNode#execute to use it. --- .../selenium-ide/src/neo/IO/SideeX/ext-command.js | 10 ++++++++++ packages/selenium-ide/src/neo/IO/SideeX/playback.js | 12 +----------- .../src/neo/playback/playback-tree/command-node.js | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js index bd881b1cd..0099713c3 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/ext-command.js @@ -386,4 +386,14 @@ export default class ExtCommand { return false; } } + + isWindowMethodCommand(command) { + return (command == "answerOnNextPrompt" + || command == "chooseCancelOnNextPrompt" + || command == "assertPrompt" + || command == "chooseOkOnNextConfirmation" + || command == "chooseCancelOnNextConfirmation" + || command == "assertConfirmation" + || command == "assertAlert"); + } } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 10dc98560..6ca2db428 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -278,7 +278,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { function doSeleniumCommand(id, command, target, value, implicitTime, implicitCount) { return (command !== "type" ? PlaybackState.currentExecutingCommandNode.execute(extCommand) - : extCommand.doType(xlateArgument(target), xlateArgument(value), isWindowMethodCommand(command))).then(function(result) { + : extCommand.doType(xlateArgument(target), xlateArgument(value), extCommand.isWindowMethodCommand(command))).then(function(result) { if (result.result !== "success") { // implicit if (isElementNotFound(result.result)) { @@ -362,16 +362,6 @@ function isReceivingEndError(reason) { reason.message == "The message port closed before a response was received." ); } -function isWindowMethodCommand(command) { - return (command == "answerOnNextPrompt" - || command == "chooseCancelOnNextPrompt" - || command == "assertPrompt" - || command == "chooseOkOnNextConfirmation" - || command == "chooseCancelOnNextConfirmation" - || command == "assertConfirmation" - || command == "assertAlert"); -} - function isImplicitWait(command) { return (command == "waitForPageToLoad" || command == "waitForElementPresent"); diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index e04631152..e43c93f56 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -46,7 +46,7 @@ export class CommandNode { }; }); } else { - return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false).then((result) => { + return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, extCommand.isWindowMethodCommand(this.command.command)).then((result) => { if (result.result === "success") { return { result: "success", From d7c4d3fd91733f24c3a165da0ce466885cf3d86a Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 06:48:35 -0400 Subject: [PATCH 060/125] Fixed a bug introduced by commandLevels when deleting tests and test suites. Made it so commandLevels is an empty array by default, and updated with levels if props.commands is present. --- packages/selenium-ide/src/neo/components/TestTable/index.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/components/TestTable/index.jsx b/packages/selenium-ide/src/neo/components/TestTable/index.jsx index 44ec307c5..78dcafc9d 100644 --- a/packages/selenium-ide/src/neo/components/TestTable/index.jsx +++ b/packages/selenium-ide/src/neo/components/TestTable/index.jsx @@ -35,6 +35,7 @@ export default class TestTable extends React.Component { this.detectNewCommand = this.detectNewCommand.bind(this); this.disposeNewCommand = this.disposeNewCommand.bind(this); this.newObserverDisposer = observe(this.props.commands, this.detectNewCommand); + this.commandLevels = []; } static propTypes = { commands: MobxPropTypes.arrayOrObservableArray, @@ -61,7 +62,7 @@ export default class TestTable extends React.Component { } } render() { - const commandLevels = deriveCommandLevels(this.props.commands); + if (this.props.commands) this.commandLevels = deriveCommandLevels(this.props.commands); const commandStatePrefix = this.props.callstackIndex !== undefined ? `${this.props.callstackIndex}:` : ""; return ([
@@ -99,7 +100,7 @@ export default class TestTable extends React.Component { pasteFromClipboard={UiState.pasteFromClipboard} clearAllCommands={this.props.clearAllCommands} setSectionFocus={UiState.setSectionFocus} - level={commandLevels[index]} + level={this.commandLevels[index]} /> )).concat( Date: Sun, 22 Jul 2018 09:26:16 -0400 Subject: [PATCH 061/125] Added a suite and tests for control flow commands. --- packages/selenium-ide/src/neo/stores/seed.js | 41 +++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/packages/selenium-ide/src/neo/stores/seed.js b/packages/selenium-ide/src/neo/stores/seed.js index 6b8ec1457..6fb2af291 100644 --- a/packages/selenium-ide/src/neo/stores/seed.js +++ b/packages/selenium-ide/src/neo/stores/seed.js @@ -66,15 +66,6 @@ export default function seed(store, numberOfSuites = 5) { const secondClick = playbackTest.createCommand(); secondClick.setCommand("clickAt"); secondClick.setTarget("link=parliamentary systems"); - const ifCommand = playbackTest.createCommand(); - ifCommand.setCommand("if"); - ifCommand.setTarget("true"); - playbackTest.createCommand(undefined, "echo", "foo"); - playbackTest.createCommand().setCommand("else"); - const leftCommand = playbackTest.createCommand(); - leftCommand.setCommand("echo"); - leftCommand.setTarget("bar"); - playbackTest.createCommand().setCommand("end"); const playbackTest2 = store.createTestCase("aab playback"); const open2 = playbackTest2.createCommand(); @@ -90,6 +81,32 @@ export default function seed(store, numberOfSuites = 5) { thirdClick2.setCommand("clickAt"); thirdClick2.setTarget("link=scapegoat"); + const controlFlowIfTest = store.createTestCase("control flow if else"); + controlFlowIfTest.createCommand(undefined, "open", "/wiki/River_Chater"); + controlFlowIfTest.createCommand(undefined, "if", "true"); + controlFlowIfTest.createCommand(undefined, "echo", "foo"); + controlFlowIfTest.createCommand(undefined, "else"); + controlFlowIfTest.createCommand(undefined, "echo", "bar"); + controlFlowIfTest.createCommand(undefined, "end"); + + const controlFlowRepeatIfTest = store.createTestCase("control flow repeat if"); + controlFlowRepeatIfTest.createCommand(undefined, "open", "/wiki/River_Chater"); + controlFlowRepeatIfTest.createCommand(undefined, "do"); + controlFlowRepeatIfTest.createCommand(undefined, "echo", "foo"); + controlFlowRepeatIfTest.createCommand(undefined, "repeat if", "true", "2"); + + const controlFlowTimesTest = store.createTestCase("control flow times"); + controlFlowTimesTest.createCommand(undefined, "open", "/wiki/River_Chater"); + controlFlowTimesTest.createCommand(undefined, "times", "2"); + controlFlowTimesTest.createCommand(undefined, "echo", "foo"); + controlFlowTimesTest.createCommand(undefined, "end"); + + const controlFlowWhileTest = store.createTestCase("control flow while"); + controlFlowWhileTest.createCommand(undefined, "open", "/wiki/River_Chater"); + controlFlowWhileTest.createCommand(undefined, "while", "true", "2"); + controlFlowWhileTest.createCommand(undefined, "echo", "foo"); + controlFlowWhileTest.createCommand(undefined, "end"); + const typeTest = store.createTestCase("aab type"); const open3 = typeTest.createCommand(); open3.setCommand("open"); @@ -113,6 +130,12 @@ export default function seed(store, numberOfSuites = 5) { suite2.addTestCase(typeTest); suite2.addTestCase(playbackTest2); + const suiteControlFlow = store.createSuite("control flow"); + suiteControlFlow.addTestCase(controlFlowIfTest); + suiteControlFlow.addTestCase(controlFlowRepeatIfTest); + suiteControlFlow.addTestCase(controlFlowTimesTest); + suiteControlFlow.addTestCase(controlFlowWhileTest); + UiState.selectTest(playbackTest); store.changeName("project"); From 04c632d0856bdf69ad14146c8594b5dc29c9fe7b Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 09:47:36 -0400 Subject: [PATCH 062/125] Added breaking on a potential infinite loop with optional override. Added a predefined expression for the times command so just a number needs to be provided as the target. Also cleaned up the code styling a bit so it's a little easier to read. --- .../playback/playback-tree/command-node.js | 75 +++++++++++++------ 1 file changed, 52 insertions(+), 23 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index e43c93f56..ceda0dd46 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -33,12 +33,17 @@ export class CommandNode { } execute(extCommand) { + this.timesVisited++; + this.checkRetryLimit(); if (extCommand.isExtCommand(this.command.command)) { - return extCommand[extCommand.name(this.command.command)](xlateArgument(this.command.target), xlateArgument(this.command.value)).then(() => { - return { - next: this.next - }; - }); + return extCommand[extCommand.name(this.command.command)]( + xlateArgument(this.command.target), + xlateArgument(this.command.value)) + .then(() => { + return { + next: this.next + }; + }); } else if (this.isControlFlow()) { return this.evaluate(extCommand).then((result) => { return { @@ -46,30 +51,54 @@ export class CommandNode { }; }); } else { - return extCommand.sendMessage(this.command.command, this.command.target, this.command.value, extCommand.isWindowMethodCommand(this.command.command)).then((result) => { - if (result.result === "success") { + return extCommand.sendMessage( + this.command.command, + this.command.target, + this.command.value, + extCommand.isWindowMethodCommand(this.command.command)) + .then((result) => { + if (result.result === "success") { + return { + result: "success", + next: this.next + }; + } else { + return result; + } + }); + } + } + + evaluate(extCommand) { + let expression = this.command.target; + if (this.command.command === "times") { + if (isNaN(this.command.target)) { + throw Error("Number not provided in target input field of the times command."); + } + expression = `${this.timesVisited} <= ${this.command.target}`; + } + return extCommand.sendMessage( + "evaluateConditional", expression, "", false) + .then((result) => { + if (result.value) { return { - result: "success", - next: this.next + next: this.right }; } else { - return result; + return { + next: this.left + }; } }); - } } - evaluate(extCommand) { - return extCommand.sendMessage("evaluateConditional", this.command.target, "", false).then((result) => { - if (result.value) { - return { - next: this.right - }; - } else { - return { - next: this.left - }; - } - }); + checkRetryLimit() { + let limit = 1000; + if (this.command.value && !isNaN(this.command.value)) { + limit = this.command.value; + } + if (this.timesVisited > limit) { + throw new Error(`Max command retry limit (${limit}) exceeded. Potential infinite loop detected. To override the limit, specify an alternate number in the value input field of the looping command.`); + } } } From 3d4df75e81b048d5e7f1fd770f77da8400a65ddf Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 09:48:30 -0400 Subject: [PATCH 063/125] Made it so executing individual control flow commands throws a helpful error message. --- .../src/neo/IO/SideeX/playback.js | 20 ++++++++++++------- .../selenium-ide/src/neo/models/Command.js | 19 +++++++++++++++++- 2 files changed, 31 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 6ca2db428..68b1f7aac 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -24,6 +24,7 @@ import { canExecuteCommand, executeCommand } from "../../../plugin/commandExecut import ExtCommand from "./ext-command"; import { xlateArgument } from "./formatCommand"; import { createPlaybackTree } from "../../playback/playback-tree"; +import { ControlFlowCommandChecks } from "../../models/Command"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -52,8 +53,13 @@ function play(currUrl) { function initPlaybackTree() { try { - playbackTree = createPlaybackTree(PlaybackState.runningQueue); - PlaybackState.setCurrentExecutingCommandNode(playbackTree.currentCommandNode); + if (PlaybackState.runningQueue.length === 1 && + ControlFlowCommandChecks.isControlFlow(PlaybackState.runningQueue[0].command)) { + throw new Error("Unable to execute control flow command by itself. Instead, you can play the test from here by right-clicking and selecting 'Play from here'."); + } else { + playbackTree = createPlaybackTree(PlaybackState.runningQueue); + PlaybackState.setCurrentExecutingCommandNode(playbackTree.currentCommandNode); + } } catch (error) { reportError(error.message, false, error.index); } @@ -87,11 +93,7 @@ function executionLoop() { else if (ignoreBreakpoint) ignoreBreakpoint = false; // paused if (isStopping()) return false; - if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { - return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { - PlaybackState.setCurrentExecutingCommandNode(result.next); - }).then(executionLoop); - } else if (extCommand.isExtCommand(command.command)) { + if (extCommand.isExtCommand(command.command)) { return doDelay().then(() => { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)) .then((result) => { @@ -100,6 +102,10 @@ function executionLoop() { PlaybackState.setCurrentExecutingCommandNode(result.next); }).then(executionLoop); }); + } else if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { + return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { + PlaybackState.setCurrentExecutingCommandNode(result.next); + }).then(executionLoop); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); return executionLoop(); diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 07c772ed7..c3fa2e121 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -858,6 +858,22 @@ function isTerminal(command) { isEnd(command)); } +function isControlFlow(command) { + switch(command) { + case ControlFlowCommandNames.if: + case ControlFlowCommandNames.elseIf: + case ControlFlowCommandNames.else: + case ControlFlowCommandNames.end: + case ControlFlowCommandNames.do: + case ControlFlowCommandNames.repeatIf: + case ControlFlowCommandNames.times: + case ControlFlowCommandNames.while: + return true; + default: + return false; + } +} + export const ControlFlowCommandChecks = { isConditional: isConditional, isDo: isDo, @@ -867,6 +883,7 @@ export const ControlFlowCommandChecks = { isIf: isIf, isLoop: isLoop, isBlockOpen: isBlockOpen, - isTerminal: isTerminal + isTerminal: isTerminal, + isControlFlow: isControlFlow }; From 72c4297b5bc7344b9725fdafeae6d3bd74a77182 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 09:57:13 -0400 Subject: [PATCH 064/125] Modified error copy --- packages/selenium-ide/src/neo/IO/SideeX/playback.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 68b1f7aac..c9ca0b653 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -55,7 +55,7 @@ function initPlaybackTree() { try { if (PlaybackState.runningQueue.length === 1 && ControlFlowCommandChecks.isControlFlow(PlaybackState.runningQueue[0].command)) { - throw new Error("Unable to execute control flow command by itself. Instead, you can play the test from here by right-clicking and selecting 'Play from here'."); + throw new Error("Unable to execute control flow command by itself. You can execute this command by running the entire test or right-clicking on the command and selecting 'Play from here'."); } else { playbackTree = createPlaybackTree(PlaybackState.runningQueue); PlaybackState.setCurrentExecutingCommandNode(playbackTree.currentCommandNode); From e39d4e61f5baa1caa1de735ac098050faf8c79fe Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 12:57:51 -0400 Subject: [PATCH 065/125] Wired up errors from 1) hitting the max retry and 2) not providing a number for times to #reportError in playback.js. Also fixed the issue where some commands weren't showing up in the log pane (tl;dr typos are the worst). --- .../src/neo/IO/SideeX/playback.js | 21 ++++++------- .../playback/playback-tree/command-node.js | 30 +++++++++++++------ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index c9ca0b653..a26ac02e6 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -39,7 +39,6 @@ extCommand.doSetSpeed = (speed) => { let baseUrl = ""; let ignoreBreakpoint = false; -let playbackTree; function play(currUrl) { baseUrl = currUrl; @@ -55,9 +54,12 @@ function initPlaybackTree() { try { if (PlaybackState.runningQueue.length === 1 && ControlFlowCommandChecks.isControlFlow(PlaybackState.runningQueue[0].command)) { - throw new Error("Unable to execute control flow command by itself. You can execute this command by running the entire test or right-clicking on the command and selecting 'Play from here'."); + reportError( + "Unable to execute control flow command by itself. You can execute this command by running the entire test or by right-clicking on the command and selecting 'Play from here'.", + false, + 0); } else { - playbackTree = createPlaybackTree(PlaybackState.runningQueue); + let playbackTree = createPlaybackTree(PlaybackState.runningQueue); PlaybackState.setCurrentExecutingCommandNode(playbackTree.currentCommandNode); } } catch (error) { @@ -104,6 +106,9 @@ function executionLoop() { }); } else if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { + if (result.result !== "success") { + reportError(result.result, false, undefined); + } PlaybackState.setCurrentExecutingCommandNode(result.next); }).then(executionLoop); } else if (isImplicitWait(command)) { @@ -146,14 +151,10 @@ function catchPlayingError(message) { function reportError(error, nonFatal, index) { let id; - if (index) { + if (!isNaN(index)) { id = PlaybackState.runningQueue[index].id; - } else { - if (PlaybackState.currentlyExecutingCommandNode) { - id = PlaybackState.currentlyExecutingCommandNode.command.id; - } else { - console.log(error); - } + } else if (PlaybackState.currentExecutingCommandNode) { + id = PlaybackState.currentExecutingCommandNode.command.id; } let message = error; if (error.message === "this.playingFrameLocations[this.currentPlayingTabId] is undefined") { diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index ceda0dd46..742696101 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -34,7 +34,12 @@ export class CommandNode { execute(extCommand) { this.timesVisited++; - this.checkRetryLimit(); + if (this.isRetryLimit()) { + return Promise.resolve({ + result: "Max retry limit exceeded. To override it, specify a new limit\ + in the value input field." + }); + } if (extCommand.isExtCommand(this.command.command)) { return extCommand[extCommand.name(this.command.command)]( xlateArgument(this.command.target), @@ -46,9 +51,14 @@ export class CommandNode { }); } else if (this.isControlFlow()) { return this.evaluate(extCommand).then((result) => { - return { - next: result.next - }; + if (result.result === "success") { + return { + result: "success", + next: result.next + }; + } else { + return result; + } }); } else { return extCommand.sendMessage( @@ -73,7 +83,9 @@ export class CommandNode { let expression = this.command.target; if (this.command.command === "times") { if (isNaN(this.command.target)) { - throw Error("Number not provided in target input field of the times command."); + return Promise.resolve({ + result: "Invalid number provided as a target." + }); } expression = `${this.timesVisited} <= ${this.command.target}`; } @@ -82,23 +94,23 @@ export class CommandNode { .then((result) => { if (result.value) { return { + result: "success", next: this.right }; } else { return { + result: "success", next: this.left }; } }); } - checkRetryLimit() { + isRetryLimit() { let limit = 1000; if (this.command.value && !isNaN(this.command.value)) { limit = this.command.value; } - if (this.timesVisited > limit) { - throw new Error(`Max command retry limit (${limit}) exceeded. Potential infinite loop detected. To override the limit, specify an alternate number in the value input field of the looping command.`); - } + return (this.timesVisited > limit); } } From 207d7976df6bc97eadc44e832e5b5f562a3b153e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Sun, 22 Jul 2018 13:46:41 -0400 Subject: [PATCH 066/125] Fixed an edge case bug where an empty conditional branch (e.g., while-end) would try to execute the end keyword through sendMessage. Also cleaned up the control flow checks in models/Command.js. --- .../__test__/playback/playback-tree.spec.js | 15 ++++- .../selenium-ide/src/neo/models/Command.js | 63 ++++++++++++------- .../playback/playback-tree/command-node.js | 8 ++- .../src/neo/playback/playback-tree/index.js | 3 +- .../playback-tree/syntax-validation.js | 4 +- 5 files changed, 65 insertions(+), 28 deletions(-) diff --git a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js index ed1a178c1..0213bda81 100644 --- a/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js +++ b/packages/selenium-ide/src/neo/__test__/playback/playback-tree.spec.js @@ -86,6 +86,19 @@ describe("Control Flow", () => { expect(stack[6].right).toBeUndefined(); expect(stack[6].left).toBeUndefined(); }); + test("while-end", () => { + let input = [ + createCommand(ControlFlowCommandNames.while), + createCommand(ControlFlowCommandNames.end) + ]; + let stack = createCommandNodesFromCommandStack(input); + expect(stack[0].next).toBeUndefined(); + expect(stack[0].right).toBeUndefined(); + expect(stack[0].left).toBeUndefined(); + expect(stack[1].next).toBeUndefined(); + expect(stack[1].right).toBeUndefined(); + expect(stack[1].left).toBeUndefined(); + }); test("while-command-end", () => { let input = [ createCommand(ControlFlowCommandNames.while), @@ -294,7 +307,7 @@ describe("Control Flow", () => { expect(stack[2].right).toEqual(stack[3]); expect(stack[2].left).toEqual(stack[7]); expect(stack[3].next).toBeUndefined(); - expect(stack[3].right).toEqual(stack[4]); + expect(stack[3].right).toBeUndefined(); expect(stack[3].left).toEqual(stack[7]); expect(stack[4].next).toBeUndefined(); expect(stack[4].right).toBeUndefined(); diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index c3fa2e121..573d28f6f 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -818,8 +818,37 @@ function commandNamesEqual(command, target) { } } +function isBlockOpen(command) { + return (isIf(command) || isLoop(command)); +} + function isConditional(command) { - return (isIf(command) || isElseOrElseIf(command)); + switch(command) { + case ControlFlowCommandNames.elseIf: + case ControlFlowCommandNames.if: + case ControlFlowCommandNames.repeatIf: + case ControlFlowCommandNames.times: + case ControlFlowCommandNames.while: + return true; + default: + return false; + } +} + +function isControlFlow(command) { + switch(command) { + case ControlFlowCommandNames.if: + case ControlFlowCommandNames.elseIf: + case ControlFlowCommandNames.else: + case ControlFlowCommandNames.end: + case ControlFlowCommandNames.do: + case ControlFlowCommandNames.repeatIf: + case ControlFlowCommandNames.times: + case ControlFlowCommandNames.while: + return true; + default: + return false; + } } function isDo(command) { @@ -830,9 +859,12 @@ function isElse(command) { return commandNamesEqual(command, ControlFlowCommandNames.else); } +function isElseIf(command) { + return commandNamesEqual(command, ControlFlowCommandNames.elseIf); +} + function isElseOrElseIf(command) { - return (commandNamesEqual(command, ControlFlowCommandNames.else) || - commandNamesEqual(command, ControlFlowCommandNames.elseIf)); + return (isElseIf(command) || isElse(command)); } function isEnd(command) { @@ -843,38 +875,23 @@ function isIf(command) { return (commandNamesEqual(command, ControlFlowCommandNames.if)); } +function isIfBlock(command) { + return (isIf(command) || isElseOrElseIf(command)); +} + function isLoop(command) { return (commandNamesEqual(command, ControlFlowCommandNames.while) || commandNamesEqual(command, ControlFlowCommandNames.times)); } -function isBlockOpen(command) { - return (isIf(command) || isLoop(command)); -} - function isTerminal(command) { return (isElse(command) || isDo(command) || isEnd(command)); } -function isControlFlow(command) { - switch(command) { - case ControlFlowCommandNames.if: - case ControlFlowCommandNames.elseIf: - case ControlFlowCommandNames.else: - case ControlFlowCommandNames.end: - case ControlFlowCommandNames.do: - case ControlFlowCommandNames.repeatIf: - case ControlFlowCommandNames.times: - case ControlFlowCommandNames.while: - return true; - default: - return false; - } -} - export const ControlFlowCommandChecks = { + isIfBlock: isIfBlock, isConditional: isConditional, isDo: isDo, isElse: isElse, diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 742696101..1ca2370b3 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -16,6 +16,7 @@ // under the License. import { xlateArgument } from "../../IO/SideeX/formatCommand"; +import { ControlFlowCommandChecks } from "../../models/Command"; export class CommandNode { constructor(command) { @@ -29,7 +30,12 @@ export class CommandNode { } isControlFlow() { - return !!(this.left || this.right); + return !!( + this.left || + this.right || + // for cases where it is a conditional command, but no left/right is set + ControlFlowCommandChecks.isConditional(this.command.command) + ); } execute(extCommand) { diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/index.js b/packages/selenium-ide/src/neo/playback/playback-tree/index.js index 709906c64..d72743404 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/index.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/index.js @@ -101,7 +101,8 @@ function connectConditionalForBranchOpen (commandNode, nextCommandNode, stack, s function connectConditional (commandNode, nextCommandNode, stack) { let left = findNextNodeBy(stack, commandNode.index, commandNode.level); - commandNode.right = nextCommandNode; + let right = ControlFlowCommandChecks.isEnd(nextCommandNode.command) ? undefined : nextCommandNode; + commandNode.right = right; commandNode.left = deriveNext(left, stack); } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js b/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js index 1471ce896..c92c67987 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/syntax-validation.js @@ -49,7 +49,7 @@ function trackControlFlowBranchOpen (commandName, commandIndex, state) { } function validateElse (commandName, commandIndex, state) { - if (!ControlFlowCommandChecks.isConditional(state.top())) { + if (!ControlFlowCommandChecks.isIfBlock(state.top())) { throw new ControlFlowSyntaxError("An else used outside of an if block", commandIndex); } if (ControlFlowCommandChecks.isElse(state.top())) { @@ -59,7 +59,7 @@ function validateElse (commandName, commandIndex, state) { } function validateElseIf (commandName, commandIndex, state) { - if (!ControlFlowCommandChecks.isConditional(state.top())) { + if (!ControlFlowCommandChecks.isIfBlock(state.top())) { throw new ControlFlowSyntaxError("An else if used outside of an if block", commandIndex); } if (ControlFlowCommandChecks.isElse(state.top())) { From 26ee833fb735cd73eec17873b4975de1de248ae6 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 23 Jul 2018 06:30:31 -0400 Subject: [PATCH 067/125] Renamed `elseIf` to `else if` in models/Command.js, added 'else if' to the seed test for `if`, and cleaned up the control flow test names a little. --- .../selenium-ide/src/neo/models/Command.js | 2 +- packages/selenium-ide/src/neo/stores/seed.js | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/selenium-ide/src/neo/models/Command.js b/packages/selenium-ide/src/neo/models/Command.js index 573d28f6f..d86922d01 100644 --- a/packages/selenium-ide/src/neo/models/Command.js +++ b/packages/selenium-ide/src/neo/models/Command.js @@ -413,7 +413,7 @@ class CommandList { description: "Part of an if block. Execute the commands in this branch when an if and/or else if condition are not met. Terminate the branch with the end command." }], [ "elseIf", { - name: "elseIf", + name: "else if", type: TargetTypes.LOCATOR, description: "Part of an if block. Execute the commands in this branch when an if condition has not been met. Terminate the branch with the end command." }], diff --git a/packages/selenium-ide/src/neo/stores/seed.js b/packages/selenium-ide/src/neo/stores/seed.js index 6fb2af291..719b748eb 100644 --- a/packages/selenium-ide/src/neo/stores/seed.js +++ b/packages/selenium-ide/src/neo/stores/seed.js @@ -81,19 +81,21 @@ export default function seed(store, numberOfSuites = 5) { thirdClick2.setCommand("clickAt"); thirdClick2.setTarget("link=scapegoat"); - const controlFlowIfTest = store.createTestCase("control flow if else"); + const controlFlowIfTest = store.createTestCase("control flow if"); controlFlowIfTest.createCommand(undefined, "open", "/wiki/River_Chater"); controlFlowIfTest.createCommand(undefined, "if", "true"); controlFlowIfTest.createCommand(undefined, "echo", "foo"); - controlFlowIfTest.createCommand(undefined, "else"); + controlFlowIfTest.createCommand(undefined, "else if", "true"); controlFlowIfTest.createCommand(undefined, "echo", "bar"); + controlFlowIfTest.createCommand(undefined, "else"); + controlFlowIfTest.createCommand(undefined, "echo", "baz"); controlFlowIfTest.createCommand(undefined, "end"); - const controlFlowRepeatIfTest = store.createTestCase("control flow repeat if"); - controlFlowRepeatIfTest.createCommand(undefined, "open", "/wiki/River_Chater"); - controlFlowRepeatIfTest.createCommand(undefined, "do"); - controlFlowRepeatIfTest.createCommand(undefined, "echo", "foo"); - controlFlowRepeatIfTest.createCommand(undefined, "repeat if", "true", "2"); + const controlFlowDoTest = store.createTestCase("control flow do"); + controlFlowDoTest.createCommand(undefined, "open", "/wiki/River_Chater"); + controlFlowDoTest.createCommand(undefined, "do"); + controlFlowDoTest.createCommand(undefined, "echo", "foo"); + controlFlowDoTest.createCommand(undefined, "repeat if", "true", "2"); const controlFlowTimesTest = store.createTestCase("control flow times"); controlFlowTimesTest.createCommand(undefined, "open", "/wiki/River_Chater"); @@ -132,7 +134,7 @@ export default function seed(store, numberOfSuites = 5) { const suiteControlFlow = store.createSuite("control flow"); suiteControlFlow.addTestCase(controlFlowIfTest); - suiteControlFlow.addTestCase(controlFlowRepeatIfTest); + suiteControlFlow.addTestCase(controlFlowDoTest); suiteControlFlow.addTestCase(controlFlowTimesTest); suiteControlFlow.addTestCase(controlFlowWhileTest); From a46cedce1c42f32d69e190341be963648972780a Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 24 Jul 2018 12:48:50 -0400 Subject: [PATCH 068/125] First pass on sandbox eval (Chrome only). --- .../selenium-ide/src/content/sandbox.html | 27 +++++++++ packages/selenium-ide/src/content/sandbox.js | 58 +++++++++++++++++++ packages/selenium-ide/src/manifest.json | 4 ++ .../src/neo/IO/SideeX/playback.js | 21 +++++-- packages/selenium-ide/src/neo/index.html | 1 + .../playback/playback-tree/command-node.js | 36 +++++++----- .../src/neo/stores/view/PlaybackState.js | 1 + packages/selenium-ide/webpack.config.babel.js | 3 +- 8 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 packages/selenium-ide/src/content/sandbox.html create mode 100644 packages/selenium-ide/src/content/sandbox.js diff --git a/packages/selenium-ide/src/content/sandbox.html b/packages/selenium-ide/src/content/sandbox.html new file mode 100644 index 000000000..1b63105dc --- /dev/null +++ b/packages/selenium-ide/src/content/sandbox.html @@ -0,0 +1,27 @@ + + diff --git a/packages/selenium-ide/src/content/sandbox.js b/packages/selenium-ide/src/content/sandbox.js new file mode 100644 index 000000000..dd5dad9ee --- /dev/null +++ b/packages/selenium-ide/src/content/sandbox.js @@ -0,0 +1,58 @@ +// 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. + +class Sandbox { + constructor() { + this.result; + this.iframe = document.getElementById("sandbox"); + window.addEventListener("message", (event) => { + if (event.data.result) { + this.result = event.data.result; + } + }); + } + + eval(expression) { + const message = { + command: "evaluateConditional", + expression: expression + }; + this.iframe.contentWindow.postMessage(message, "*"); + return new Promise(resolve => { + setTimeout(() => { + if (this.result) { + resolve(this.stringToBool(this.result)); + } + }, 50); + }); + } + + stringToBool(_input) { + let input = { ..._input }; + switch(input.value) { + case "truthy": + input.value = true; + break; + case "falsy": + input.value = false; + break; + } + return input; + } +} + +export default new Sandbox; diff --git a/packages/selenium-ide/src/manifest.json b/packages/selenium-ide/src/manifest.json index 5f26cad60..ce8665813 100644 --- a/packages/selenium-ide/src/manifest.json +++ b/packages/selenium-ide/src/manifest.json @@ -53,5 +53,9 @@ "background": { "scripts": ["assets/background.js"] + }, + "sandbox": + { + "pages": ["assets/sandbox.html"] } } diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 928f716ce..9cfe01c6d 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -25,6 +25,7 @@ import ExtCommand from "./ext-command"; import { xlateArgument } from "./formatCommand"; import { createPlaybackTree } from "../../playback/playback-tree"; import { ControlFlowCommandChecks } from "../../models/Command"; +//import { Sandbox } from "../../../content/sandbox"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -39,6 +40,7 @@ extCommand.doSetSpeed = (speed) => { let baseUrl = ""; let ignoreBreakpoint = false; +//const sandbox = new Sandbox; function play(currUrl) { baseUrl = currUrl; @@ -102,15 +104,22 @@ function executionLoop() { // we need to set the stackIndex manually because run command messes with that PlaybackState.setCommandStateAtomically(command.id, stackIndex, PlaybackStates.Passed); PlaybackState.setCurrentExecutingCommandNode(result.next); + if (command.command === "open") { + PlaybackState.isOpenCommandUsed = true; + } }).then(executionLoop); }); } else if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { - return (PlaybackState.currentExecutingCommandNode.execute(extCommand)).then((result) => { - if (result.result !== "success") { - reportError(result.result, false, undefined); - } - PlaybackState.setCurrentExecutingCommandNode(result.next); - }).then(executionLoop); + return (PlaybackState.isOpenCommandUsed ? + PlaybackState.currentExecutingCommandNode.execute(extCommand) + : + PlaybackState.currentExecutingCommandNode.execute(extCommand, false)) + .then((result) => { + if (result.result !== "success") { + reportError(result.result, false, undefined); + } + PlaybackState.setCurrentExecutingCommandNode(result.next); + }).then(executionLoop); } else if (isImplicitWait(command)) { notifyWaitDeprecation(command); return executionLoop(); diff --git a/packages/selenium-ide/src/neo/index.html b/packages/selenium-ide/src/neo/index.html index 2b127bf53..fbd4212db 100644 --- a/packages/selenium-ide/src/neo/index.html +++ b/packages/selenium-ide/src/neo/index.html @@ -4,6 +4,7 @@ + Selenium IDE diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 1ca2370b3..2a21bbc17 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -17,6 +17,7 @@ import { xlateArgument } from "../../IO/SideeX/formatCommand"; import { ControlFlowCommandChecks } from "../../models/Command"; +import sandbox from "../../../content/sandbox"; export class CommandNode { constructor(command) { @@ -34,11 +35,12 @@ export class CommandNode { this.left || this.right || // for cases where it is a conditional command, but no left/right is set + // e.g., an empty conditional branch (e.g., while/end) ControlFlowCommandChecks.isConditional(this.command.command) ); } - execute(extCommand) { + execute(extCommand, evalInContentWindow) { this.timesVisited++; if (this.isRetryLimit()) { return Promise.resolve({ @@ -56,7 +58,7 @@ export class CommandNode { }; }); } else if (this.isControlFlow()) { - return this.evaluate(extCommand).then((result) => { + return this.evaluate(extCommand, evalInContentWindow).then((result) => { if (result.result === "success") { return { result: "success", @@ -85,7 +87,7 @@ export class CommandNode { } } - evaluate(extCommand) { + evaluate(extCommand, evalInContentWindow = true) { let expression = this.command.target; if (this.command.command === "times") { if (isNaN(this.command.target)) { @@ -95,19 +97,25 @@ export class CommandNode { } expression = `${this.timesVisited} <= ${this.command.target}`; } - return extCommand.sendMessage( - "evaluateConditional", expression, "", false) + return (evalInContentWindow ? + extCommand.sendMessage("evaluateConditional", expression, "", false) + : + sandbox.eval(expression)) .then((result) => { - if (result.value) { - return { - result: "success", - next: this.right - }; + if (result.result === "success") { + if (result.value) { + return { + result: "success", + next: this.right + }; + } else { + return { + result: "success", + next: this.left + }; + } } else { - return { - result: "success", - next: this.left - }; + return result; } }); } diff --git a/packages/selenium-ide/src/neo/stores/view/PlaybackState.js b/packages/selenium-ide/src/neo/stores/view/PlaybackState.js index 8322382a1..5894a4808 100644 --- a/packages/selenium-ide/src/neo/stores/view/PlaybackState.js +++ b/packages/selenium-ide/src/neo/stores/view/PlaybackState.js @@ -49,6 +49,7 @@ class PlaybackState { @observable delay = 0; @observable callstack = []; @observable currentExecutingCommandNode = null; + @observable isOpenCommandUsed = false; constructor() { this.maxDelay = 3000; diff --git a/packages/selenium-ide/webpack.config.babel.js b/packages/selenium-ide/webpack.config.babel.js index eb9cddb3e..6609b90fa 100644 --- a/packages/selenium-ide/webpack.config.babel.js +++ b/packages/selenium-ide/webpack.config.babel.js @@ -242,7 +242,8 @@ export default { { from: "content/prompt.css", to: "./" }, { from: "content/bootstrap.html", to: "./" }, { from: "manifest.json", to: "../" }, - { from: "icons", to: "../icons" } + { from: "icons", to: "../icons" }, + { from: "content/sandbox.html", to: "./" } ]), // Generates an `index.html` file with the + window.addEventListener('message', function(event) { + const command = event.data.command; + const name = event.data.name || 'hello'; + switch(command) { + case 'evaluateConditional': + event.source.postMessage({ + name: name, + result: evaluate(event.data.expression) + }, event.origin); + break; + } + }); + + + From c32d0f77d34a8707ce59dd16e68ed96a9b63057c Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 24 Jul 2018 14:26:42 -0400 Subject: [PATCH 072/125] Moved iframe loading to bottom of body. --- packages/selenium-ide/src/neo/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/neo/index.html b/packages/selenium-ide/src/neo/index.html index fbd4212db..c32b0f582 100644 --- a/packages/selenium-ide/src/neo/index.html +++ b/packages/selenium-ide/src/neo/index.html @@ -4,11 +4,11 @@ - Selenium IDE
+ From ff22cc706287dc8af87f14415e56bc0468234372 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 24 Jul 2018 14:29:01 -0400 Subject: [PATCH 073/125] Added error reporting for sandbox eval on Firefox. Also added resetting the open command tracking variable when playing. --- packages/selenium-ide/src/neo/IO/SideeX/playback.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index dcc637a52..4a82c390a 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -25,7 +25,7 @@ import ExtCommand from "./ext-command"; import { xlateArgument } from "./formatCommand"; import { createPlaybackTree } from "../../playback/playback-tree"; import { ControlFlowCommandChecks } from "../../models/Command"; -//import { Sandbox } from "../../../content/sandbox"; +import parser from "ua-parser-js"; export const extCommand = new ExtCommand(); // In order to not break the separation of the execution loop from the state of the playback @@ -40,7 +40,7 @@ extCommand.doSetSpeed = (speed) => { let baseUrl = ""; let ignoreBreakpoint = false; -//const sandbox = new Sandbox; +const browserName = parser(window.navigator.userAgent).browser.name; function play(currUrl) { baseUrl = currUrl; @@ -108,6 +108,9 @@ function executionLoop() { }).then(executionLoop); }); } else if (PlaybackState.currentExecutingCommandNode.isControlFlow()) { + if (browserName === "Firefox" && !PlaybackState.isOpenCommandUsed) { + reportError("Expression evaluation prior to an 'open' command is not supported in Firefox.", false, undefined); + } return (PlaybackState.isOpenCommandUsed ? PlaybackState.currentExecutingCommandNode.execute(extCommand) : @@ -134,6 +137,7 @@ function executionLoop() { } function prepareToPlay() { + PlaybackState.isOpenCommandUsed = false; // to support deleting an open command and re-running the test to trigger sandbox eval return extCommand.init(baseUrl); } From 5f662946c45a0f33161ea20b473ddd96ed8b47e2 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 25 Jul 2018 06:45:46 -0400 Subject: [PATCH 074/125] Swapped copyright header --- .../selenium-ide/src/content/sandbox.html | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/selenium-ide/src/content/sandbox.html b/packages/selenium-ide/src/content/sandbox.html index 23fb55daa..579e85536 100644 --- a/packages/selenium-ide/src/content/sandbox.html +++ b/packages/selenium-ide/src/content/sandbox.html @@ -1,8 +1,20 @@ - +// 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. + From d0a73a5bac526621d13a21eaa23f1b89db1687be Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 25 Jul 2018 10:05:16 -0400 Subject: [PATCH 075/125] Swapped the switch/case in `stringToBool` for a one-liner and swapped `setTimeout` with returning a promise that's referenced in the result event listener. Also added string to integer conversion for `times`. --- packages/selenium-ide/src/content/sandbox.js | 29 ++++++++----------- .../playback/playback-tree/command-node.js | 5 ++-- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/selenium-ide/src/content/sandbox.js b/packages/selenium-ide/src/content/sandbox.js index dd5dad9ee..17b2856a0 100644 --- a/packages/selenium-ide/src/content/sandbox.js +++ b/packages/selenium-ide/src/content/sandbox.js @@ -20,37 +20,32 @@ class Sandbox { this.result; this.iframe = document.getElementById("sandbox"); window.addEventListener("message", (event) => { - if (event.data.result) { - this.result = event.data.result; + if (event.data.result && this.resolve) { + const result = this.stringToBool(event.data.result); + this.resolve(result); + this.resolve = undefined; } }); } eval(expression) { + if (this.resolve) { + return Promise.resolve({ result: "Cannot eval while a different eval is taking place." }); + } const message = { command: "evaluateConditional", expression: expression }; - this.iframe.contentWindow.postMessage(message, "*"); - return new Promise(resolve => { - setTimeout(() => { - if (this.result) { - resolve(this.stringToBool(this.result)); - } - }, 50); + const promise = new Promise(resolve => { + this.resolve = resolve; }); + this.iframe.contentWindow.postMessage(message, "*"); + return promise; } stringToBool(_input) { let input = { ..._input }; - switch(input.value) { - case "truthy": - input.value = true; - break; - case "falsy": - input.value = false; - break; - } + input.value = input.value === "truthy"; return input; } } diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 2a21bbc17..3b31f6ffd 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -90,12 +90,13 @@ export class CommandNode { evaluate(extCommand, evalInContentWindow = true) { let expression = this.command.target; if (this.command.command === "times") { - if (isNaN(this.command.target)) { + expression = Math.floor(+expression); + if (isNaN(expression)) { return Promise.resolve({ result: "Invalid number provided as a target." }); } - expression = `${this.timesVisited} <= ${this.command.target}`; + expression = `${this.timesVisited} <= ${expression}`; } return (evalInContentWindow ? extCommand.sendMessage("evaluateConditional", expression, "", false) From 41ad7c9e80bf6f1be252a075b0565b0d37e7b806 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 25 Jul 2018 14:43:14 -0400 Subject: [PATCH 076/125] Made value explicit --- packages/selenium-ide/src/content/commands-api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/selenium-ide/src/content/commands-api.js b/packages/selenium-ide/src/content/commands-api.js index 3ba5cf476..fdb3f34e1 100644 --- a/packages/selenium-ide/src/content/commands-api.js +++ b/packages/selenium-ide/src/content/commands-api.js @@ -45,7 +45,7 @@ function doCommands(request, sender, sendResponse) { } else if (request.commands === "evaluateConditional") { try { let value = selenium["doEvaluateConditional"](request.target); - sendResponse({ result: "success", value }); + sendResponse({ result: "success", value: value }); } catch(e) { sendResponse({ result: e.message }); } From d7e5cc553acad54436a070639e644b4df23da199 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Wed, 25 Jul 2018 14:44:12 -0400 Subject: [PATCH 077/125] Moved the eval return into its own variable --- packages/selenium-ide/src/content/sandbox.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/content/sandbox.html b/packages/selenium-ide/src/content/sandbox.html index 579e85536..668cb65e2 100644 --- a/packages/selenium-ide/src/content/sandbox.html +++ b/packages/selenium-ide/src/content/sandbox.html @@ -18,13 +18,14 @@ - + diff --git a/packages/selenium-ide/src/content/sandbox.js b/packages/selenium-ide/src/content/sandbox.js index 36f7497b8..9f85bdfc9 100644 --- a/packages/selenium-ide/src/content/sandbox.js +++ b/packages/selenium-ide/src/content/sandbox.js @@ -15,25 +15,32 @@ // specific language governing permissions and limitations // under the License. +import browser from "webextension-polyfill"; + class Sandbox { constructor() { this.result; window.addEventListener("message", (event) => { if (event.data.result && this.resolve) { - const result = this.stringToBool(event.data.result); + const result = this.command === "evaluateConditional" ? + this.stringToBool(event.data.result) : event.data.result; + if (result.varName) storeVariable(result).then(handleResponse, handleError); this.resolve(result); this.resolve = undefined; } }); } - eval(expression) { + sendMessage(command, expression, varName) { if (this.resolve) { return Promise.resolve({ result: "Cannot eval while a different eval is taking place." }); } + this.command = command; + if (varName === "") varName = undefined; const message = { - command: "evaluateConditional", - expression: expression + command: this.command, + expression: expression, + varName: varName }; const promise = new Promise(resolve => { this.resolve = resolve; @@ -50,8 +57,23 @@ class Sandbox { stringToBool(_input) { let input = { ..._input }; input.value = input.value === "truthy"; - return input; - } + return input; } +} + + +function storeVariable(result) { + return browser.runtime.sendMessage({ + "storeStr": result.value, + "storeVar": result.varName + }); +} + +function handleResponse(message) { + console.log(`Result message: ${message.response}`); +} + +function handleError(error) { + console.log(`Error: ${error.message}`); } export default new Sandbox; diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 0878f070c..ffd4d1f12 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -51,7 +51,7 @@ export class CommandNode { } } - execute(extCommand, evalInContentWindow) { + execute(extCommand, evalInContentWindow = true) { if (this._isRetryLimit()) { return Promise.resolve({ result: "Max retry limit exceeded. To override it, specify a new limit in the value input field." @@ -69,6 +69,12 @@ export class CommandNode { xlateArgument(this.command.value)); } else if (this.isControlFlow()) { return this._evaluate(extCommand, evalInContentWindow); + } else if (this.command.command === "executeScript" || + this.command.command === "executeAsyncScript") { + return (evalInContentWindow ? + extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false) + : + sandbox.sendMessage(this.command.command, this.command.target, this.command.value)); } else { return extCommand.sendMessage( this.command.command, @@ -98,7 +104,7 @@ export class CommandNode { if (ControlFlowCommandChecks.isLoop(this.command)) this.timesVisited++; } - _evaluate(extCommand, evalInContentWindow = true) { + _evaluate(extCommand, evalInContentWindow) { let expression = this.command.target; if (ControlFlowCommandChecks.isTimes(this.command)) { const number = Math.floor(+expression); @@ -112,7 +118,7 @@ export class CommandNode { return (evalInContentWindow ? extCommand.sendMessage("evaluateConditional", expression, "", false) : - sandbox.eval(expression)) + sandbox.sendMessage("evaluateConditional", expression)) .then((result) => { return this._evaluationResult(result); }); From fa6ebc57a75cf5adc0a6e540dcee6346a3d15906 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 30 Jul 2018 08:37:56 -0400 Subject: [PATCH 099/125] First pass on code emitting for control flow commands. --- packages/selianize/__tests__/command.spec.js | 64 ++++++++++++++++++++ packages/selianize/src/command.js | 48 ++++++++++++--- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/packages/selianize/__tests__/command.spec.js b/packages/selianize/__tests__/command.spec.js index 474edf85c..bdea60b55 100644 --- a/packages/selianize/__tests__/command.spec.js +++ b/packages/selianize/__tests__/command.spec.js @@ -682,4 +682,68 @@ describe("command code emitter", () => { const snapshot = "this commands snapshot"; return expect(CommandEmitter.emit(command, undefined, snapshot)).resolves.toBe(snapshot); }); + it("should emit `if` command", () => { + const command = { + command: "if", + target: "true", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe(`if (${command.target}) {`); + }); + it("should emit `else if` command", () => { + const command = { + command: "elseIf", + target: "true", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe(`} else if (${command.target}) {`); + }); + it("should emit `else` command", () => { + const command = { + command: "else", + target: "true", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe("} else {"); + }); + it("should emit `times` command", () => { + const command = { + command: "times", + target: "5", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe(`const times = ${command.target};for(let i = 0; i < times; i++) {`); + }); + it("should emit `while` command", () => { + const command = { + command: "while", + target: "true", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe(`while (${command.target}) {`); + }); + it("should emit `end` command", () => { + const command = { + command: "end", + target: "", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe("}"); + }); + it("should emit `do` command", () => { + const command = { + command: "do", + target: "", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe("do {"); + }); + it("should emit `repeatIf` command", () => { + const command = { + command: "repeatIf", + target: "true", + value: "" + }; + return expect(CommandEmitter.emit(command)).resolves.toBe(`} while(${command.target});`); + }); }); diff --git a/packages/selianize/src/command.js b/packages/selianize/src/command.js index 4066d3db9..dcafd7634 100644 --- a/packages/selianize/src/command.js +++ b/packages/selianize/src/command.js @@ -95,14 +95,14 @@ const emitters = { chooseCancelOnNextPrompt: skip, chooseOkOnNextConfirmation: skip, setSpeed: skip, - do: emitControlFlow, - else: emitControlFlow, - elseIf: emitControlFlow, - end: emitControlFlow, - if: emitControlFlow, - repeatIf: emitControlFlow, - times: emitControlFlow, - while: emitControlFlow + do: emitControlFlowDo, + else: emitControlFlowElse, + elseIf: emitControlFlowElseIf, + end: emitControlFlowEnd, + if: emitControlFlowIf, + repeatIf: emitControlFlowRepeatIf, + times: emitControlFlowTimes, + while: emitControlFlowWhile }; export function emit(command, options = config, snapshot) { @@ -354,4 +354,34 @@ function skip() { return Promise.resolve(); } -function emitControlFlow() { } +function emitControlFlowDo() { + return Promise.resolve("do {"); +} + +function emitControlFlowElse() { + return Promise.resolve(`} else {`); +} + +function emitControlFlowElseIf(target) { + return Promise.resolve(`} else if (${target}) {`); +} + +function emitControlFlowEnd() { + return Promise.resolve("}"); +} + +function emitControlFlowIf(target) { + return Promise.resolve(`if (${target}) {`); +} + +function emitControlFlowRepeatIf(target) { + return Promise.resolve(`} while(${target});`); +} + +function emitControlFlowTimes(target) { + return Promise.resolve(`const times = ${target};for(let i = 0; i < times; i++) {`); +} + +function emitControlFlowWhile(target) { + return Promise.resolve(`while (${target}) {`); +} From 929f4b78ac195d2b32398130a3658d0c4b86738e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 30 Jul 2018 14:46:09 -0400 Subject: [PATCH 100/125] Defaulting seed.js to load the control flow if test for simplicity. --- packages/selenium-ide/src/neo/stores/seed.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/selenium-ide/src/neo/stores/seed.js b/packages/selenium-ide/src/neo/stores/seed.js index 06bb20e89..d4ddb472a 100644 --- a/packages/selenium-ide/src/neo/stores/seed.js +++ b/packages/selenium-ide/src/neo/stores/seed.js @@ -79,9 +79,8 @@ export default function seed(store, numberOfSuites = 5) { secondClick2.setTarget("link=floods of 1947"); const thirdClick2 = playbackTest2.createCommand(); thirdClick2.setCommand("clickAt"); - thirdClick2.setTarget("link=scapegoat"); + thirdClick2.setTarget("link=scapegoat"); const controlFlowIfTest = store.createTestCase("control flow if"); - controlFlowIfTest.createCommand(undefined, "open", "/wiki/River_Chater"); controlFlowIfTest.createCommand(undefined, "if", "true"); controlFlowIfTest.createCommand(undefined, "echo", "foo"); controlFlowIfTest.createCommand(undefined, "else if", "true"); @@ -178,9 +177,9 @@ export default function seed(store, numberOfSuites = 5) { }); UiState.changeView("Test suites"); - let suiteState = UiState.getSuiteState(suiteExecuteScript); + let suiteState = UiState.getSuiteState(suiteControlFlow); suiteState.setOpen(true); - UiState.selectTest(executeScriptSandboxTest, suiteExecuteScript); + UiState.selectTest(controlFlowIfTest, suiteControlFlow); store.changeName("project"); From 3890bace8c4746ec1e6f09efe77c9e3c13caee95 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Mon, 30 Jul 2018 14:55:52 -0400 Subject: [PATCH 101/125] Moved away from sanbox eval in favor of loading up bootstrap.html with the appropriate assets to receive commands. Works, but has CSP issues which are likely non-starters. Will revisit tomorrow. --- .../selenium-ide/src/content/bootstrap.html | 22 +++++++++++++++++ packages/selenium-ide/src/content/sandbox.js | 1 - packages/selenium-ide/src/manifest.json | 2 +- .../src/neo/IO/SideeX/playback.js | 4 +++- .../playback/playback-tree/command-node.js | 24 +++++++++++-------- 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/selenium-ide/src/content/bootstrap.html b/packages/selenium-ide/src/content/bootstrap.html index ea9c2ef25..969339a07 100644 --- a/packages/selenium-ide/src/content/bootstrap.html +++ b/packages/selenium-ide/src/content/bootstrap.html @@ -21,4 +21,26 @@

Preparing to run your test

+ diff --git a/packages/selenium-ide/src/content/sandbox.js b/packages/selenium-ide/src/content/sandbox.js index 9f85bdfc9..5e9ada00a 100644 --- a/packages/selenium-ide/src/content/sandbox.js +++ b/packages/selenium-ide/src/content/sandbox.js @@ -60,7 +60,6 @@ class Sandbox { return input; } } - function storeVariable(result) { return browser.runtime.sendMessage({ "storeStr": result.value, diff --git a/packages/selenium-ide/src/manifest.json b/packages/selenium-ide/src/manifest.json index 44fa3ee40..9605017a2 100644 --- a/packages/selenium-ide/src/manifest.json +++ b/packages/selenium-ide/src/manifest.json @@ -42,7 +42,7 @@ "assets/prompt.css", "assets/bootstrap.html" ], - "content_security_policy": "script-src 'self'; object-src 'self'", + "content_security_policy": "script-src 'self' 'sha256-xwpNteOvgDqJluuRVZmsCD8P57UOCOAsd1nDZTsFSgE=' 'unsafe-eval'; object-src 'self'", "content_scripts": [ { "matches": [ diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index f8f6af09c..3d5f8237e 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -102,6 +102,7 @@ function executionLoop() { else if (ignoreBreakpoint) ignoreBreakpoint = false; // paused if (isStopping()) return false; + console.log(command.command); if (extCommand.isExtCommand(command.command)) { return doDelay().then(() => { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)) @@ -295,7 +296,7 @@ function doCommand(res, implicitTime = Date.now(), implicitCount = 0) { function doSeleniumCommand(id, command, target, value, implicitTime, implicitCount) { return (command !== "type" - ? PlaybackState.currentExecutingCommandNode.execute(extCommand, PlaybackState.isOpenCommandUsed) + ? PlaybackState.currentExecutingCommandNode.execute(extCommand) : extCommand.doType(xlateArgument(target), xlateArgument(value), extCommand.isWindowMethodCommand(command))).then(function(result) { if (result.result !== "success") { // implicit @@ -372,6 +373,7 @@ function notifyWaitDeprecation(command) { } function isReceivingEndError(reason) { + console.log(reason); return (reason == "TypeError: response is undefined" || reason == "Error: Could not establish connection. Receiving end does not exist." || // Below message is for Google Chrome diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index ffd4d1f12..ab04a33f8 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -69,12 +69,12 @@ export class CommandNode { xlateArgument(this.command.value)); } else if (this.isControlFlow()) { return this._evaluate(extCommand, evalInContentWindow); - } else if (this.command.command === "executeScript" || - this.command.command === "executeAsyncScript") { - return (evalInContentWindow ? - extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false) - : - sandbox.sendMessage(this.command.command, this.command.target, this.command.value)); + //} else if (this.command.command === "executeScript" || + //} this.command.command === "executeAsyncScript") { + //} return (evalInContentWindow ? + //} extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false) + //} : + //} sandbox.sendMessage(this.command.command, this.command.target, this.command.value)); } else { return extCommand.sendMessage( this.command.command, @@ -115,10 +115,14 @@ export class CommandNode { } expression = `${this.timesVisited} < ${number}`; } - return (evalInContentWindow ? - extCommand.sendMessage("evaluateConditional", expression, "", false) - : - sandbox.sendMessage("evaluateConditional", expression)) + //return (evalInContentWindow ? + // extCommand.sendMessage("evaluateConditional", expression, "", false) + // : + // sandbox.sendMessage("evaluateConditional", expression)) + // .then((result) => { + // return this._evaluationResult(result); + // }); + return (extCommand.sendMessage("evaluateConditional", expression, "", false)) .then((result) => { return this._evaluationResult(result); }); From ce8a97314093eaed491c9548440d76cc4cd8bb2e Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 31 Jul 2018 09:43:15 -0400 Subject: [PATCH 102/125] Removed console output from debugging --- packages/selenium-ide/src/neo/IO/SideeX/playback.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/IO/SideeX/playback.js b/packages/selenium-ide/src/neo/IO/SideeX/playback.js index 3d5f8237e..a08e003c2 100644 --- a/packages/selenium-ide/src/neo/IO/SideeX/playback.js +++ b/packages/selenium-ide/src/neo/IO/SideeX/playback.js @@ -102,7 +102,6 @@ function executionLoop() { else if (ignoreBreakpoint) ignoreBreakpoint = false; // paused if (isStopping()) return false; - console.log(command.command); if (extCommand.isExtCommand(command.command)) { return doDelay().then(() => { return (PlaybackState.currentExecutingCommandNode.execute(extCommand)) @@ -373,7 +372,6 @@ function notifyWaitDeprecation(command) { } function isReceivingEndError(reason) { - console.log(reason); return (reason == "TypeError: response is undefined" || reason == "Error: Could not establish connection. Receiving end does not exist." || // Below message is for Google Chrome From 4ea0a77ee24e010c5aa7e9cc42b5aecc44d03c54 Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 31 Jul 2018 09:45:59 -0400 Subject: [PATCH 103/125] Removed the `evalInContentWindow` parameter and commented out code from sandbox eval. Added `xlateArgument` to the block of `_executeCommand` used for executing Selenium commands (fixes a regression where `executeScript` wasn't storing variables) --- .../playback/playback-tree/command-node.js | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index ab04a33f8..51a2a6a69 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -51,35 +51,29 @@ export class CommandNode { } } - execute(extCommand, evalInContentWindow = true) { + execute(extCommand) { if (this._isRetryLimit()) { return Promise.resolve({ result: "Max retry limit exceeded. To override it, specify a new limit in the value input field." }); } - return this._executeCommand(extCommand, evalInContentWindow).then((result) => { + return this._executeCommand(extCommand).then((result) => { return this._executionResult(extCommand, result); }); } - _executeCommand(extCommand, evalInContentWindow) { + _executeCommand(extCommand) { if (extCommand.isExtCommand(this.command.command)) { return extCommand[extCommand.name(this.command.command)]( xlateArgument(this.command.target), xlateArgument(this.command.value)); } else if (this.isControlFlow()) { - return this._evaluate(extCommand, evalInContentWindow); - //} else if (this.command.command === "executeScript" || - //} this.command.command === "executeAsyncScript") { - //} return (evalInContentWindow ? - //} extCommand.sendMessage(this.command.command, this.command.target, this.command.value, false) - //} : - //} sandbox.sendMessage(this.command.command, this.command.target, this.command.value)); + return this._evaluate(extCommand); } else { return extCommand.sendMessage( this.command.command, - this.command.target, - this.command.value, + xlateArgument(this.command.target), + xlateArgument(this.command.value), extCommand.isWindowMethodCommand(this.command.command)); } } @@ -104,7 +98,7 @@ export class CommandNode { if (ControlFlowCommandChecks.isLoop(this.command)) this.timesVisited++; } - _evaluate(extCommand, evalInContentWindow) { + _evaluate(extCommand) { let expression = this.command.target; if (ControlFlowCommandChecks.isTimes(this.command)) { const number = Math.floor(+expression); @@ -115,13 +109,6 @@ export class CommandNode { } expression = `${this.timesVisited} < ${number}`; } - //return (evalInContentWindow ? - // extCommand.sendMessage("evaluateConditional", expression, "", false) - // : - // sandbox.sendMessage("evaluateConditional", expression)) - // .then((result) => { - // return this._evaluationResult(result); - // }); return (extCommand.sendMessage("evaluateConditional", expression, "", false)) .then((result) => { return this._evaluationResult(result); From ac8de08b7c197e7d0316b64fbdf2c9e089867ebf Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 31 Jul 2018 10:22:23 -0400 Subject: [PATCH 104/125] Fixed linter errors --- .../selenium-ide/src/neo/playback/playback-tree/command-node.js | 1 - packages/selianize/src/command.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js index 51a2a6a69..e3161e14a 100644 --- a/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js +++ b/packages/selenium-ide/src/neo/playback/playback-tree/command-node.js @@ -17,7 +17,6 @@ import { xlateArgument } from "../../IO/SideeX/formatCommand"; import { ControlFlowCommandChecks } from "../../models/Command"; -import sandbox from "../../../content/sandbox"; export class CommandNode { constructor(command) { diff --git a/packages/selianize/src/command.js b/packages/selianize/src/command.js index dcafd7634..e2a4965c6 100644 --- a/packages/selianize/src/command.js +++ b/packages/selianize/src/command.js @@ -359,7 +359,7 @@ function emitControlFlowDo() { } function emitControlFlowElse() { - return Promise.resolve(`} else {`); + return Promise.resolve("} else {"); } function emitControlFlowElseIf(target) { From 224593124da072cb6636861d2821f1dcee24b4fc Mon Sep 17 00:00:00 2001 From: David Haeffner Date: Tue, 31 Jul 2018 10:40:07 -0400 Subject: [PATCH 105/125] Removed the sandbox --- .../src/__test__/content/sandbox.spec.js | 33 ------- .../selenium-ide/src/content/sandbox.html | 95 ------------------- packages/selenium-ide/src/content/sandbox.js | 78 --------------- packages/selenium-ide/src/manifest.json | 4 - packages/selenium-ide/webpack.config.babel.js | 3 +- 5 files changed, 1 insertion(+), 212 deletions(-) delete mode 100644 packages/selenium-ide/src/__test__/content/sandbox.spec.js delete mode 100644 packages/selenium-ide/src/content/sandbox.html delete mode 100644 packages/selenium-ide/src/content/sandbox.js diff --git a/packages/selenium-ide/src/__test__/content/sandbox.spec.js b/packages/selenium-ide/src/__test__/content/sandbox.spec.js deleted file mode 100644 index 29badab2a..000000000 --- a/packages/selenium-ide/src/__test__/content/sandbox.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -// 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 sandbox from "../../content/sandbox"; - -describe("Sandbox", () => { - it("eval resolves with an error message when another eval is taking place", () => { - sandbox.resolve = true; - sandbox.sendMessage().then((result) => { - expect(result.result).toEqual("Cannot eval while a different eval is taking place."); - }); - }); - it("eval resolves with an error message when the sandbox iframe is not present", () => { - sandbox.resolve = undefined; - sandbox.sendMessage().then((result) => { - expect(result.result).toEqual("Expression evaluation prior to an 'open' command is not supported in Firefox."); - }); - }); -}); diff --git a/packages/selenium-ide/src/content/sandbox.html b/packages/selenium-ide/src/content/sandbox.html deleted file mode 100644 index 4101c5e5a..000000000 --- a/packages/selenium-ide/src/content/sandbox.html +++ /dev/null @@ -1,95 +0,0 @@ -// 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. - - - - - - - - - - diff --git a/packages/selenium-ide/src/content/sandbox.js b/packages/selenium-ide/src/content/sandbox.js deleted file mode 100644 index 5e9ada00a..000000000 --- a/packages/selenium-ide/src/content/sandbox.js +++ /dev/null @@ -1,78 +0,0 @@ -// 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 browser from "webextension-polyfill"; - -class Sandbox { - constructor() { - this.result; - window.addEventListener("message", (event) => { - if (event.data.result && this.resolve) { - const result = this.command === "evaluateConditional" ? - this.stringToBool(event.data.result) : event.data.result; - if (result.varName) storeVariable(result).then(handleResponse, handleError); - this.resolve(result); - this.resolve = undefined; - } - }); - } - - sendMessage(command, expression, varName) { - if (this.resolve) { - return Promise.resolve({ result: "Cannot eval while a different eval is taking place." }); - } - this.command = command; - if (varName === "") varName = undefined; - const message = { - command: this.command, - expression: expression, - varName: varName - }; - const promise = new Promise(resolve => { - this.resolve = resolve; - }); - const iframe = document.getElementById("sandbox"); - if (iframe) { - iframe.contentWindow.postMessage(message, "*"); - return promise; - } else { - return Promise.resolve({ result: "Expression evaluation prior to an 'open' command is not supported in Firefox." }); - } - } - - stringToBool(_input) { - let input = { ..._input }; - input.value = input.value === "truthy"; - return input; } -} - -function storeVariable(result) { - return browser.runtime.sendMessage({ - "storeStr": result.value, - "storeVar": result.varName - }); -} - -function handleResponse(message) { - console.log(`Result message: ${message.response}`); -} - -function handleError(error) { - console.log(`Error: ${error.message}`); -} - -export default new Sandbox; diff --git a/packages/selenium-ide/src/manifest.json b/packages/selenium-ide/src/manifest.json index 9605017a2..de677ab86 100644 --- a/packages/selenium-ide/src/manifest.json +++ b/packages/selenium-ide/src/manifest.json @@ -64,9 +64,5 @@ "background": { "scripts": ["assets/background.js"] - }, - "sandbox": - { - "pages": ["assets/sandbox.html"] } } diff --git a/packages/selenium-ide/webpack.config.babel.js b/packages/selenium-ide/webpack.config.babel.js index 6609b90fa..eb9cddb3e 100644 --- a/packages/selenium-ide/webpack.config.babel.js +++ b/packages/selenium-ide/webpack.config.babel.js @@ -242,8 +242,7 @@ export default { { from: "content/prompt.css", to: "./" }, { from: "content/bootstrap.html", to: "./" }, { from: "manifest.json", to: "../" }, - { from: "icons", to: "../icons" }, - { from: "content/sandbox.html", to: "./" } + { from: "icons", to: "../icons" } ]), // Generates an `index.html` file with the