Implement assertions and get basic assertion test working
This commit is contained in:
parent
46a9bd477a
commit
ba7e99f482
12 changed files with 300 additions and 71 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -154,6 +154,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"headless_chrome",
|
||||
"log 0.4.17",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
|
|
1
cef-test-core/Cargo.lock
generated
1
cef-test-core/Cargo.lock
generated
|
@ -144,6 +144,7 @@ dependencies = [
|
|||
"chrono",
|
||||
"headless_chrome",
|
||||
"log 0.4.17",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"ureq",
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
regex = "1"
|
||||
chrono = { version = "0.4" }
|
||||
log = "0.4"
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@ pub trait TestAdapter {
|
|||
/// Scroll to element in tab
|
||||
fn element_scroll_to(&mut self, tab: &TabSelector, element: &ElementSelector) -> Feedback;
|
||||
|
||||
/// Retrieve text in element in tab
|
||||
fn element_value(&mut self, tab: &TabSelector, element: &ElementSelector) -> Feedback;
|
||||
|
||||
/// Retrieve text in element in tab
|
||||
fn element_attribute(&mut self, tab: &TabSelector, element: &ElementSelector, attribute: &str) -> Feedback;
|
||||
|
||||
/// Pause execution in tab for a period
|
||||
fn wait(&mut self, tab: &TabSelector, milliseconds: u64) -> Feedback;
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ pub enum Feedback {
|
|||
Start,
|
||||
/// Last instruction was successful
|
||||
Success,
|
||||
/// Last instruction returned a value
|
||||
Value(serde_json::Value),
|
||||
/// Last instruction was an assertion and it failed
|
||||
AssertFailure,
|
||||
/// Last instruction raised an error
|
||||
|
@ -18,6 +20,7 @@ impl Feedback {
|
|||
match self {
|
||||
Self::Success => true,
|
||||
Self::Start => true,
|
||||
Self::Value(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -29,4 +32,14 @@ impl Feedback {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Feedback is indicative of a failing test
|
||||
pub fn is_fail(&self) -> bool {
|
||||
match self {
|
||||
Self::AssertFailure => true,
|
||||
Self::Error => true,
|
||||
Self::Unsupported => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +1,73 @@
|
|||
use super::{TestRunner, TestAdapter, TestMetadata};
|
||||
use super::{Instruction, TestAssert, TestOp, Feedback, GeneralOpType, ElementOpType, BasicOpType};
|
||||
use super::{Instruction, TestAssert, TestOp, Feedback, GeneralOpType, ElementOpType, TabOpType, GeneralAssertType, ElementAssertionType, TabAssert, Comparison};
|
||||
|
||||
/// Harness which runs one or more tests
|
||||
pub struct TestHarness<R: TestRunner, A: TestAdapter> {
|
||||
tests: Vec<R>,
|
||||
adaptor: A,
|
||||
adapter: A,
|
||||
}
|
||||
|
||||
impl<R: TestRunner, A: TestAdapter> TestHarness<R, A> {
|
||||
/// Construct a new test harness
|
||||
pub fn new(adaptor: A, tests: Vec<R>) -> Self {
|
||||
pub fn new(adapter: A, tests: Vec<R>) -> Self {
|
||||
Self {
|
||||
adaptor,
|
||||
adapter,
|
||||
tests,
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_assertion(&mut self, _assertion: TestAssert) -> Feedback {
|
||||
// TODO
|
||||
Feedback::Success
|
||||
fn translate_assertion(&mut self, assertion: TestAssert) -> Feedback {
|
||||
match assertion.assertion {
|
||||
GeneralAssertType::Element(elem) => {
|
||||
match elem.assert {
|
||||
ElementAssertionType::Value(comparison) =>
|
||||
Self::maybe_assert(
|
||||
self.adapter.element_value(&assertion.context, &elem.element),
|
||||
comparison
|
||||
),
|
||||
ElementAssertionType::Attribute { attribute, comparison } =>
|
||||
Self::maybe_assert(
|
||||
self.adapter.element_attribute(&assertion.context, &elem.element, &attribute),
|
||||
comparison
|
||||
)
|
||||
}
|
||||
},
|
||||
GeneralAssertType::Tab(TabAssert::Evaluate { script, comparison }) => Self::maybe_assert(self.adapter.evaluate(&assertion.context, &script), comparison)
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_assert(adaptor_feedback: Feedback, cmp: Comparison) -> Feedback {
|
||||
if let Feedback::Value(v) = &adaptor_feedback {
|
||||
if cmp.compare(Some(v)) {
|
||||
log::info!("Assertion satisfied: {}", cmp.pseudocode_assert(Some(v)));
|
||||
adaptor_feedback
|
||||
} else {
|
||||
log::error!("Assertion failed: {}", cmp.pseudocode_assert(Some(v)));
|
||||
Feedback::AssertFailure
|
||||
}
|
||||
} else {
|
||||
if cmp.compare(None) {
|
||||
log::info!("Assertion satisfied: {}", cmp.pseudocode_assert(None));
|
||||
adaptor_feedback
|
||||
} else {
|
||||
log::error!("Assertion failed: {}", cmp.pseudocode_assert(None));
|
||||
Feedback::AssertFailure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_ui_op(&mut self, op: TestOp) -> Feedback {
|
||||
// TODO
|
||||
match op.op {
|
||||
GeneralOpType::Element(elem) => {
|
||||
match elem.op {
|
||||
ElementOpType::Click => self.adaptor.element_click(&op.context, &elem.context),
|
||||
ElementOpType::WaitFor => self.adaptor.element_wait(&op.context, &elem.context),
|
||||
ElementOpType::Focus => self.adaptor.element_focus(&op.context, &elem.context),
|
||||
ElementOpType::ScrollTo => self.adaptor.element_scroll_to(&op.context, &elem.context),
|
||||
ElementOpType::Click => self.adapter.element_click(&op.context, &elem.context),
|
||||
ElementOpType::WaitFor => self.adapter.element_wait(&op.context, &elem.context),
|
||||
ElementOpType::Focus => self.adapter.element_focus(&op.context, &elem.context),
|
||||
ElementOpType::ScrollTo => self.adapter.element_scroll_to(&op.context, &elem.context),
|
||||
}
|
||||
},
|
||||
GeneralOpType::Basic(BasicOpType::Sleep(ms)) => self.adaptor.wait(&op.context, ms),
|
||||
GeneralOpType::Basic(BasicOpType::Evaluate(js)) => self.adaptor.evaluate(&op.context, &js),
|
||||
GeneralOpType::Tab(TabOpType::Sleep(ms)) => self.adapter.wait(&op.context, ms),
|
||||
GeneralOpType::Tab(TabOpType::Evaluate(js)) => self.adapter.evaluate(&op.context, &js),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +101,7 @@ impl<R: TestRunner, A: TestAdapter> TestHarness<R, A> {
|
|||
}
|
||||
}
|
||||
if failures.is_empty() {
|
||||
Ok(self.adaptor)
|
||||
Ok(self.adapter)
|
||||
} else {
|
||||
Err(failures)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use regex::Regex;
|
||||
use headless_chrome::{Browser, Tab, Element};
|
||||
|
||||
use crate::cef::WebContent;
|
||||
|
@ -29,7 +30,7 @@ impl HeadlessAdapter {
|
|||
conn_map.insert(web_contents[0].title().to_owned(), browser);
|
||||
}
|
||||
}
|
||||
//std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
std::thread::sleep(std::time::Duration::from_millis(1_000));
|
||||
log::info!("HeadlessAdapter ready");
|
||||
Ok(Self {
|
||||
web_content: web_contents,
|
||||
|
@ -43,8 +44,7 @@ impl HeadlessAdapter {
|
|||
if let Some(browser) = self.connections.get(title) {
|
||||
for tab in browser.get_tabs().lock().map_err(|e| format!("{}", e))?.iter() {
|
||||
if let Ok(info) = tab.get_target_info() {
|
||||
dbg!(&info.title, title == &info.title);
|
||||
if title == &info.title {
|
||||
if info.title == title {
|
||||
return Ok(Some(tab.clone()));
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,15 @@ impl HeadlessAdapter {
|
|||
for web_content in self.web_content.iter() {
|
||||
let is_match = match tab_select {
|
||||
TabSelector::Title(title) => title == web_content.title(),
|
||||
TabSelector::TitleRegex(pattern) => {
|
||||
let pattern = Regex::new(pattern).map_err(|x| x.to_string())?;
|
||||
pattern.is_match(web_content.title())
|
||||
},
|
||||
TabSelector::Url(url) => url == web_content.url(),
|
||||
TabSelector::UrlRegex(pattern) => {
|
||||
let pattern = Regex::new(pattern).map_err(|x| x.to_string())?;
|
||||
pattern.is_match(web_content.url())
|
||||
},
|
||||
TabSelector::Id(id) => id == web_content.id(),
|
||||
};
|
||||
if is_match {
|
||||
|
@ -81,6 +89,14 @@ impl HeadlessAdapter {
|
|||
|| info.browser_context_id.map(|ctx_id| &ctx_id == id).unwrap_or(false)
|
||||
},
|
||||
TabSelector::Title(title) => title == &info.title,
|
||||
TabSelector::TitleRegex(pattern) => {
|
||||
let pattern = Regex::new(pattern).map_err(|x| x.to_string())?;
|
||||
pattern.is_match(&info.title)
|
||||
},
|
||||
TabSelector::UrlRegex(pattern) => {
|
||||
let pattern = Regex::new(pattern).map_err(|x| x.to_string())?;
|
||||
pattern.is_match(&info.url)
|
||||
},
|
||||
};
|
||||
if is_match {
|
||||
return Ok(Some(tab.clone()));
|
||||
|
@ -136,7 +152,7 @@ impl TestAdapter for HeadlessAdapter {
|
|||
fn element_click(&mut self, tab_s: &TabSelector, element_s: &ElementSelector) -> Feedback {
|
||||
// TODO better feedback
|
||||
if let Some(tab) = self.select_tab(tab_s, true) {
|
||||
if let Some(element) = self.select_element(&tab, element_s) {
|
||||
/*if let Some(element) = self.select_element(&tab, element_s) {
|
||||
match element.click() {
|
||||
Ok(_) => Feedback::Success,
|
||||
Err(e) => {
|
||||
|
@ -147,6 +163,17 @@ impl TestAdapter for HeadlessAdapter {
|
|||
} else {
|
||||
log::error!("Failed to find element {}", element_s);
|
||||
Feedback::Error
|
||||
}*/
|
||||
// FIXME element.click() doesn't actually click
|
||||
let result = match element_s {
|
||||
ElementSelector::CSS(css) => tab.evaluate(&format!("document.querySelector(\"{}\").click()", css), true),
|
||||
};
|
||||
match result {
|
||||
Ok(_) => Feedback::Success,
|
||||
Err(e) => {
|
||||
log::error!("Failed to click on element {}: {}", element_s, e);
|
||||
Feedback::Error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to find tab {}", tab_s);
|
||||
|
@ -212,6 +239,51 @@ impl TestAdapter for HeadlessAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
fn element_value(&mut self, tab_s: &TabSelector, element_s: &ElementSelector) -> Feedback {
|
||||
if let Some(tab) = self.select_tab(tab_s, true) {
|
||||
if let Some(element) = self.select_element(&tab, element_s) {
|
||||
match element.get_inner_text() {
|
||||
Ok(t) => Feedback::Value(t.into()),
|
||||
Err(e) => {
|
||||
log::error!("Failed to get inner text value of element {}: {}", element_s, e);
|
||||
Feedback::Error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to find element {}", element_s);
|
||||
Feedback::Error
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to find tab {}", tab_s);
|
||||
Feedback::Error
|
||||
}
|
||||
}
|
||||
|
||||
fn element_attribute(&mut self, tab_s: &TabSelector, element_s: &ElementSelector, _attribute: &str) -> Feedback {
|
||||
if let Some(tab) = self.select_tab(tab_s, true) {
|
||||
if let Some(element) = self.select_element(&tab, element_s) {
|
||||
match element.get_attributes() {
|
||||
Ok(Some(_attrs)) => {
|
||||
// TODO
|
||||
//attrs.get(attribute).map(|x| x.into()).unwrap_or(serde_json::Value::Null)
|
||||
Feedback::Unsupported
|
||||
},
|
||||
Ok(None) => Feedback::Value(serde_json::Value::Null),
|
||||
Err(e) => {
|
||||
log::error!("Failed to get attributes of element {}: {}", element_s, e);
|
||||
Feedback::Error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to find element {}", element_s);
|
||||
Feedback::Error
|
||||
}
|
||||
} else {
|
||||
log::error!("Failed to find tab {}", tab_s);
|
||||
Feedback::Error
|
||||
}
|
||||
}
|
||||
|
||||
fn wait(&mut self, tab_s: &TabSelector, milliseconds: u64) -> Feedback {
|
||||
// TODO better feedback
|
||||
if let Some(_tab) = self.select_tab(tab_s, true) {
|
||||
|
@ -227,7 +299,7 @@ impl TestAdapter for HeadlessAdapter {
|
|||
fn evaluate(&mut self, tab_s: &TabSelector, script: &str) -> Feedback {
|
||||
if let Some(tab) = self.select_tab(tab_s, true) {
|
||||
match tab.evaluate(script, true) {
|
||||
Ok(_) => Feedback::Success,
|
||||
Ok(result) => Feedback::Value(result.value.unwrap_or(serde_json::Value::Null)),
|
||||
Err(e) => {
|
||||
log::error!("Failed to evaluate script on tab {}: {}", tab_s, e);
|
||||
Feedback::Error
|
||||
|
|
|
@ -18,6 +18,8 @@ pub struct TestAssert {
|
|||
pub enum GeneralAssertType {
|
||||
/// Element-related assertion
|
||||
Element(ElementAssert),
|
||||
/// Tab-related assertion
|
||||
Tab(TabAssert),
|
||||
}
|
||||
|
||||
/// Element assertion
|
||||
|
@ -30,10 +32,125 @@ pub struct ElementAssert {
|
|||
|
||||
/// Assertion operations
|
||||
pub enum ElementAssertionType {
|
||||
/// Assert element exists
|
||||
/// Assert element text value
|
||||
Value(Comparison),
|
||||
/// Assert element attribute
|
||||
Attribute {
|
||||
/// Attribute name
|
||||
attribute: String,
|
||||
/// Assertion comparison mode
|
||||
comparison: Comparison
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
/// Assertion operations
|
||||
pub enum TabAssert {
|
||||
/// Run javascript and validate the result
|
||||
Evaluate {
|
||||
/// Javascript to execute
|
||||
script: String,
|
||||
/// Assertion comparison mode
|
||||
comparison: Comparison,
|
||||
}
|
||||
}
|
||||
|
||||
/// Assertion compare operation to perform
|
||||
pub enum Comparison {
|
||||
/// Assert non-null
|
||||
Exists,
|
||||
/// Assert element contains text
|
||||
TextEquals(String)
|
||||
/// Assert not empty
|
||||
ExistsNotEmpty,
|
||||
/// Assert equals expected text
|
||||
TextEquals(String),
|
||||
/// Assert contains expected string
|
||||
TextContains(String),
|
||||
/// Assert == expected value
|
||||
Equals(serde_json::Value),
|
||||
/// Assert actual != expected value
|
||||
NotEquals(serde_json::Value),
|
||||
/*
|
||||
/// Assert actual < expected value
|
||||
LessThan(serde_json::Value),
|
||||
/// Assert actual <= expected value
|
||||
LessThanEquals(serde_json::Value),
|
||||
/// Assert actual > expected value
|
||||
GreaterThan(serde_json::Value),
|
||||
/// Assert actual >= expected value
|
||||
GreaterThanEquals(serde_json::Value),
|
||||
*/
|
||||
}
|
||||
|
||||
impl Comparison {
|
||||
/// Compare actual value
|
||||
pub fn compare(&self, value: Option<&serde_json::Value>) -> bool {
|
||||
match self {
|
||||
Self::Exists => !value.is_none(),
|
||||
Self::ExistsNotEmpty => {
|
||||
if let Some(value) = value {
|
||||
if let Some(s) = value.as_str() {
|
||||
!s.is_empty()
|
||||
} else {
|
||||
!value.is_null()
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let value = value.unwrap_or(&serde_json::Value::Null);
|
||||
match self {
|
||||
Self::TextEquals(expected) => {
|
||||
if let Some(actual) = value.as_str() {
|
||||
actual.trim() == expected
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
Self::TextContains(expected) => {
|
||||
if let Some(actual) = value.as_str() {
|
||||
actual.contains(expected)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
Self::Equals(expected) => value == expected,
|
||||
Self::NotEquals(expected) => value != expected,
|
||||
|
||||
Self::Exists => unreachable!(),
|
||||
Self::ExistsNotEmpty => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Display-friendly representation of the assertion with actual and expected values
|
||||
pub fn pseudocode_assert(&self, value: Option<&serde_json::Value>) -> String {
|
||||
match value {
|
||||
Some(value) => {
|
||||
match self {
|
||||
Self::Exists => format!("{} must exist", value),
|
||||
Self::ExistsNotEmpty => format!("{} must exist", value),
|
||||
Self::TextEquals(expected) => format!("\"{}\" must equal \"{}\"", value, expected),
|
||||
Self::TextContains(expected) => format!("\"{}\" must contain \"{}\"", value, expected),
|
||||
Self::Equals(expected) => format!("{} == {}", value, expected),
|
||||
Self::NotEquals(expected) => format!("{} != {}", value, expected),
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let value = serde_json::Value::Null;
|
||||
match self {
|
||||
Self::Exists => format!("None must exist (contradiction!)"),
|
||||
Self::ExistsNotEmpty => format!("None must exist (contradiction!)"),
|
||||
Self::TextEquals(expected) => format!("\"{}\" must equal \"{}\"", value, expected),
|
||||
Self::TextContains(expected) => format!("\"{}\" must contain \"{}\"", value, expected),
|
||||
Self::Equals(expected) => format!("{} == {}", value, expected),
|
||||
Self::NotEquals(expected) => format!("{} != {}", value, expected),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// User interface interaction
|
||||
|
@ -45,6 +162,7 @@ pub struct TestOp {
|
|||
}
|
||||
|
||||
/// Element selection mode
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum ElementSelector {
|
||||
/// Use CSS selector syntax
|
||||
CSS(String),
|
||||
|
@ -62,8 +180,12 @@ impl std::fmt::Display for ElementSelector {
|
|||
pub enum TabSelector {
|
||||
/// Select by tab title
|
||||
Title(String),
|
||||
/// Select by tab title regex pattern
|
||||
TitleRegex(String),
|
||||
/// Select by tab's current URL
|
||||
Url(String),
|
||||
/// Select by tab's current URL regex pattern
|
||||
UrlRegex(String),
|
||||
/// Select by tab identifier
|
||||
Id(String),
|
||||
}
|
||||
|
@ -72,7 +194,9 @@ impl std::fmt::Display for TabSelector {
|
|||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Title(title) => write!(f, "Tab[title==`{}`]", title),
|
||||
Self::TitleRegex(title) => write!(f, "Tab[title~=`{}`]", title),
|
||||
Self::Url(url) => write!(f, "Tab[url==`{}`]", url),
|
||||
Self::UrlRegex(url) => write!(f, "Tab[url~=`{}`]", url),
|
||||
Self::Id(id) => write!(f, "Tab[id==`{}`]", id),
|
||||
}
|
||||
}
|
||||
|
@ -82,12 +206,12 @@ impl std::fmt::Display for TabSelector {
|
|||
pub enum GeneralOpType {
|
||||
/// Operate on an element
|
||||
Element(ElementOp),
|
||||
/// Basic context operation
|
||||
Basic(BasicOpType),
|
||||
/// Tab context operation
|
||||
Tab(TabOpType),
|
||||
}
|
||||
|
||||
/// Basic operation type
|
||||
pub enum BasicOpType {
|
||||
pub enum TabOpType {
|
||||
/// Pause executing thread for time, in milliseconds
|
||||
Sleep(u64),
|
||||
/// Execute Javascript in the global tab context
|
||||
|
|
|
@ -40,6 +40,7 @@ impl TestRunner for JsonRunner {
|
|||
if matches!(fail_mode, FailureMode::FastFail) && !feedback.is_ok() {
|
||||
return None;
|
||||
}
|
||||
#[allow(clippy::never_loop)]
|
||||
'step_loop: while self.step_i < self.test_data.test.len() {
|
||||
let step = &self.test_data.test[self.step_i];
|
||||
'op_loop: while self.op_i < step.operations.len() {
|
||||
|
@ -59,7 +60,6 @@ impl TestRunner for JsonRunner {
|
|||
self.op_i = 0;
|
||||
self.step_i += 1;
|
||||
}
|
||||
//log::error!("JsonRunner.next(...) is UNIMPLEMENTED!");
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::From;
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::super::TestMetadata;
|
||||
use super::super::{TabSelector, ElementSelector, ElementOpType, ElementOp, BasicOpType, GeneralOpType, GeneralAssertType, ElementAssert, ElementAssertionType, Instruction, TestAssert, TestOp};
|
||||
use super::super::{TabSelector, ElementSelector, ElementOpType, ElementOp, TabOpType, GeneralOpType, GeneralAssertType, ElementAssert, ElementAssertionType, Instruction, TestAssert, TestOp, Comparison, /*TabAssert*/};
|
||||
|
||||
/// Test descriptor
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -63,8 +63,8 @@ pub enum TabDescriptor {
|
|||
impl From<TabDescriptor> for TabSelector {
|
||||
fn from(value: TabDescriptor) -> Self {
|
||||
match value {
|
||||
TabDescriptor::Title{title: t} => Self::Title(t),
|
||||
TabDescriptor::Url{url: u} => Self::Url(u),
|
||||
TabDescriptor::Title{title: t} => Self::TitleRegex(t),
|
||||
TabDescriptor::Url{url: u} => Self::UrlRegex(u),
|
||||
TabDescriptor::Id{id: i} => Self::Id(i),
|
||||
}
|
||||
}
|
||||
|
@ -84,9 +84,13 @@ pub enum TestInstruction {
|
|||
Eval {
|
||||
/// Javascript to execute
|
||||
code: String,
|
||||
/*
|
||||
/// Result assertion
|
||||
assert: Option<Comparison>,
|
||||
*/
|
||||
},
|
||||
/// Assertion on an element
|
||||
Assert(TestAssertionInstruction),
|
||||
Assert(TestElementAssertion),
|
||||
}
|
||||
|
||||
impl TestInstruction {
|
||||
|
@ -99,11 +103,11 @@ impl TestInstruction {
|
|||
}),
|
||||
TestInstruction::Sleep { milliseconds } => Instruction::Operation(TestOp {
|
||||
context: selector,
|
||||
op: GeneralOpType::Basic(BasicOpType::Sleep(milliseconds)),
|
||||
op: GeneralOpType::Tab(TabOpType::Sleep(milliseconds)),
|
||||
}),
|
||||
TestInstruction::Eval { code } => Instruction::Operation(TestOp {
|
||||
context: selector,
|
||||
op: GeneralOpType::Basic(BasicOpType::Evaluate(code)),
|
||||
op: GeneralOpType::Tab(TabOpType::Evaluate(code)),
|
||||
}),
|
||||
TestInstruction::Assert(assertion) => Instruction::Assertion(TestAssert {
|
||||
context: selector,
|
||||
|
@ -131,13 +135,13 @@ impl From<TestElementInstruction> for ElementOp {
|
|||
|
||||
/// Test element instruction
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct TestAssertionInstruction {
|
||||
pub struct TestElementAssertion {
|
||||
pub element: ElementDescriptor,
|
||||
pub assert: ElementAssertion,
|
||||
}
|
||||
|
||||
impl From<TestAssertionInstruction> for ElementAssert {
|
||||
fn from(value: TestAssertionInstruction) -> Self {
|
||||
impl From<TestElementAssertion> for ElementAssert {
|
||||
fn from(value: TestElementAssertion) -> Self {
|
||||
Self {
|
||||
element: value.element.into(),
|
||||
assert: value.assert.into(),
|
||||
|
@ -148,6 +152,7 @@ impl From<TestAssertionInstruction> for ElementAssert {
|
|||
/// Element descriptor
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(tag = "by")]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub enum ElementDescriptor {
|
||||
/// Use CSS selector syntax
|
||||
CSS{css: String},
|
||||
|
@ -190,15 +195,18 @@ impl From<ElementInteraction> for ElementOpType {
|
|||
pub enum ElementAssertion {
|
||||
/// Assert element exists
|
||||
Exists,
|
||||
/// Assert element value equals text
|
||||
TextEquals(String),
|
||||
/// Assert element contains text
|
||||
TextEquals(String)
|
||||
TextContains(String),
|
||||
}
|
||||
|
||||
impl From<ElementAssertion> for ElementAssertionType {
|
||||
fn from(value: ElementAssertion) -> Self {
|
||||
match value {
|
||||
ElementAssertion::Exists => Self::Exists,
|
||||
ElementAssertion::TextEquals(t) => Self::TextEquals(t),
|
||||
ElementAssertion::Exists => Self::Value(Comparison::Exists),
|
||||
ElementAssertion::TextEquals(t) => Self::Value(Comparison::TextEquals(t)),
|
||||
ElementAssertion::TextContains(t) => Self::Value(Comparison::TextContains(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@ pub use adapter::TestAdapter;
|
|||
pub use feedback::Feedback;
|
||||
pub use harness::TestHarness;
|
||||
pub use headless_adapter::HeadlessAdapter;
|
||||
pub use instructions::{Instruction, TestAssert, GeneralAssertType, ElementAssert, ElementAssertionType, TestOp, ElementSelector, TabSelector, GeneralOpType, BasicOpType, ElementOp, ElementOpType};
|
||||
pub use instructions::{Instruction, TestAssert, GeneralAssertType, ElementAssert, ElementAssertionType, TestOp, ElementSelector, TabSelector, GeneralOpType, TabOpType, ElementOp, ElementOpType, TabAssert, Comparison};
|
||||
pub use json_runner::JsonRunner;
|
||||
pub use runner::{TestRunner, TestMetadata};
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
{
|
||||
"info": {
|
||||
"name": "Click on Friends",
|
||||
"blame": "NGnius",
|
||||
"id": "DeckyTest-1",
|
||||
"output": "./output.log",
|
||||
"fail_mode": "SkipSteps"
|
||||
},
|
||||
"test": [
|
||||
{
|
||||
"tab": {
|
||||
"by": "Title",
|
||||
"title": "SP"
|
||||
},
|
||||
"operations": [
|
||||
{
|
||||
"type": "Sleep",
|
||||
"milliseconds": 1000
|
||||
},
|
||||
{
|
||||
"type": "Element",
|
||||
"element": {
|
||||
"by": "CSS",
|
||||
"css": "div.gamepadtabbedpage_Tab_3eEbS.gamepadtabbedpage_HasAddon_2tufx.gamepadtabbedpage_RightAddon_KFGEk.Panel.Focusable"
|
||||
},
|
||||
"operation": "Focus"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue