js 获取dom离页面编剧_页面对象之外:具有宁静性和编剧模式的下一代测试自动化

js 获取dom离页面编剧

在当今快节奏的软件交付世界中,自动化的验收测试至关重要。 一组高质量的自动验收测试可帮助您减少浪费在手动测试和修复错误上的时间,从而更快地交付有价值的功能。 与行为驱动开发结合使用时,自动验收测试可以指导和验证开发工作,并帮助团队专注于构建真正重要的功能并确保其正常工作。

但是自动化的验收测试并不容易。 像任何其他软件开发活动一样,它需要技能,实践和纪律。 随着时间的流逝,即使是具有最佳意图的团队也可以看到其测试套件变得缓慢,脆弱且不可靠。 在现有套件中添加新测试变得越来越困难。 团队对自动化测试失去信心,损害了对测试套件的投资,并影响了团队士气。 我们通常甚至看到经验丰富的团队都在使用诸如Page Objects之类的设计模式来解决此类问题。 对于不熟悉熟练的程序员使用的模式和设计原理(例如SOLID )的团队来说,页面对象是一个很好的起点,但是在项目的早期应该考虑将强大的技术技能带给团队的重要性,以避免这些挑战。

剧本模式(以前​​称为J 模式 )是SOLID设计原则在自动验收测试中的应用, 可帮助团队解决这些问题 。 本质上,这就是使用SOLID设计原则对页面对象进行无情的重构的结果。 它最初是由安东尼·马卡诺(Antony Marcano)在2007年至2008年间设计的,并于2009年从安迪·帕尔默(Andy Palmer)的思想中进行了完善。直到2013年1月莫拉克(Jan Molak)开始使用它时,它才获得“旅途模式”的称号。这个名称已经存在,但是,作者现在将其称为“剧本模式”。

剧本模式是一种基于良好的软件工程原理(例如“ 单一职责原则”和“ 开放式封闭原则”)编写高质量自动验收测试的方法。 它偏向于组合而不是继承,并采用领域驱动设计的思想来反映执行验收测试的领域,从而引导您有效地使用抽象层。 它鼓励良好的测试习惯和易于阅读,易于维护且易于扩展的精心设计的测试套件,从而使团队能够更有效地编写更强大,更可靠的自动化测试。

Serenity BDD是一个开放源代码库,旨在帮助您编写更好,更有效的自动化验收测试,并使用这些验收测试生成高质量的测试报告和有效文档。 正如我们将在本文中看到的那样,Serenity BDD内置了对直接使用Screenplay Pattern的强大支持。

剧本操作模式

在本文的其余部分中,我们将使用Serenity BDD来说明编剧模式,尽管该模式本身在很大程度上与语言和框架无关。 我们将测试的应用程序是著名的TodoMVC项目AngularJS实现 (请参见图1)。

图1 Todo应用程序

为了简单起见,我们将Serenity BDD与JUnit一起使用,尽管我们也可以选择使用Serenity BDD与Cucumber-JVMJBehave来实现我们的自动接受标准。

现在,假设我们正在实现“添加新的待办事项”功能。 该功能可能具有“添加新待办事项”这一行的接受标准。 如果我们手动测试这些方案,则可能如下所示:

  • 添加新的待办事项
    • 从空的待办事项清单开始
    • 添加一个名为“购买牛奶”的项目
    • “购买牛奶”项应出现在待办事项列表中

剧本模式的最大卖点之一是,它使您可以建立方法和对象的可读API,以业务术语表达接受标准。 例如,使用“剧本模式”,我们可以像上面这样自然自然地自动化场景:

givenThat(james).wasAbleTo(Start.withAnEmptyTodoList());
when(james).attemptsTo(AddATodoItem.called("Buy some milk"));
then(james).should(seeThat(TheItems.displayed(), hasItem("Buy some milk")));

如果您使用过Hamcrest匹配器,则此模型很熟悉。 当我们使用Hamcrest匹配器时,我们正在创建一个匹配器的实例,该实例将在assertThat方法中进行评估。 类似地,AddATodoItem.called()返回一个“任务”的实例,该实例稍后在trysTo()方法中进行评估。 即使您不熟悉如何在后台实现该代码,也应该很清楚测试试图演示什么以及它如何实现。

我们很快将看到编写这种测试代码与阅读它一样容易。

与以更强制性的,以实现为重点的方式编写的代码相比,以类似于业务语言的方式编写的声明性代码具有更高的可维护性,并且更不容易出错。 如果代码读起来像是对业务规则的描述,那么业务逻辑中的错误就很难进入测试代码或应用程序代码本身。

此外,由Serenity为该测试生成的测试报告也反映了这种叙述结构,使测试人员,业务分析人员和业务人员更容易理解测试在业务方面的实际含义(请参见图2)。

图2:此Serenity报告记录了测试的意图和实施

上面列出的代码当然读起来很干净,但是您可能想知道它实际上是如何工作的。 让我们看看它们如何融合在一起。

剧本模式测试的运行方式与其他任何Serenity测试一样

在撰写本文时,Serenity Screenplay实现与JUnit和Cucumber集成在一起。 例如,在JUnit中,您可以像其他任何Serenity JUnit测试一样使用SerenityRunner JUnit运行器。 下面显示了我们之前看到的测试的完整源代码,其中“角色”扮演着用户与系统交互的角色:

@RunWith(SerenityRunner.class)
public class AddNewTodos {

    Actor james = Actor.named("James");

    @Managed private WebDriver hisBrowser;

    @Before
    public void jamesCanBrowseTheWeb() {
        james.can(BrowseTheWeb.with(hisBrowser));
    }

    @Test
    public void should_be_able_to_add_a_todo_item() {

        givenThat(james).wasAbleTo(Start.withAnEmptyTodoList());

        when(james).attemptsTo(AddATodoItem.called("Buy some milk"));

        then(james).should(seeThat(TheItems.displayed(), 
                                    hasItem("Buy some milk")));
    }
}

仅通过阅读代码就不难了解该测试的功能。 但是,即使您之前使用过Serenity,这里也有一些陌生的地方。 在以下各节中,我们将仔细研究细节。

剧本模式鼓励强大的抽象层次

经验丰富的自动化测试人员会使用抽象层将测试的意图 (您要实现的目标)与实现细节(如何实现)分开。 通过分离从什么 怎么样 ,从实现意图 ,抽象的助力下,层层测试更容易理解和维护。 确实,定义良好的抽象层可能是编写高质量自动化测试的最重要因素。

在用户体验(UX)设计中,我们将用户与应用程序交互的方式分解为目标任务动作

  • 目标从用户的角度试图从业务角度描述方案的“原因”。
  • 这些任务描述了用户将做什么,作为实现此目标所需的高级步骤。
  • 动作说明用户如何与系统交互以执行特定任务,例如通过单击按钮或在字段中输入值。

正如我们将看到的,“剧本模式”在目标(场景标题),任务(场景中的抽象的最高层)和动作(任务下方的最低的抽象层)之间提供了清晰的区分。更一致地编写分层测试。

剧本模式使用以演员为中心的模型

测试描述了用户如何与应用程序交互以实现目标。 因此,如果从用户的角度(而不是从“页面”的角度)呈现测试,则阅读效果会更好。

在“剧本模式”中,我们将与系统进行交互的用户称为Actor 。 演员是编剧模式的核心(见图3)。 每个参与者都具有一个或多个能力 ,例如浏览Web或查询静态Web服务的能力。 演员也可以执行任务 ,例如将项目添加到待办列表。 为了完成这些任务,他们通常需要与应用程序进行交互,例如通过在字段中输入值或单击按钮。 我们称这些交互为动作 。 参与者也可以询问有关应用程序状态的问题 ,例如通过读取屏幕上的字段值或查询Web服务。

图3:剧本模式使用以演员为中心的模型

在Serenity中,创建actor就像创建Actor类的实例并提供名称一样简单:

Actor james = Actor.named("James");

我们发现为演员提供真实姓名而不是使用通用名称(例如“用户”)很有用。 不同的名称可能是不同用户角色或角色的简写,并且使场景更易于关联。 有关使用角色的更多信息,请参见Jeff Patton的演讲“实用角色”。

演员有能力

演员需要能够做一些事情来执行他们分配的任务。 因此,我们赋予演员“能力”,有点像超级英雄的超级大国,甚至有些平凡。 例如,如果这是一个网络测试,则我们需要James能够使用浏览器浏览网络。

Serenity BDD可与Selenium WebDriver一起很好地使用,并很高兴为您管理浏览器的生命周期。 您需要做的就是对WebDriver成员变量使用@Managed批注,如下所示:

@Managed private WebDriver hisBrowser;

然后,我们可以让James使用此浏览器,如下所示:

james.can(BrowseTheWeb.with(hisBrowser));

为了清楚表明这是测试的前提(很可能使用JUnit @Before方法),我们可以使用语法糖方法namedThat():

givenThat(james).can(BrowseTheWeb.with(hisBrowser));

actor的每个功能都由一个Ability类(在本例中为BrowseTheWeb)表示,该类跟踪该actor为了执行此功能所需的事物(例如,用于与浏览器进行交互的WebDriver实例)。 将演员可以做的事情(浏览Web,调用Web服务…)与演员分开,可以更轻松地扩展演员的能力。 例如,要添加新的自定义功能,只需要将新的Ability类添加到测试类中。

演员执行任务

参与者需要执行许多任务才能实现业务目标。 一个相当典型的任务示例是“添加待办事项”,我们可以这样编写:

james.attemptsTo(AddATodoItem.called("Buy some milk"))

或者,如果任务是先决条件,而不是测试的主要主题,则我们可以这样写:

james.wasAbleTo(AddATodoItem.called("Buy some milk"))

让我们分解一下以了解发生了什么。 在剧本模式的核心,演员执行一系列任务。 在Serenity中,此机制是在Actor类中使用命令模式的一种变体实现的,其中Actor通过在相应的Task对象上调用一个称为performAs()的特殊方法来执行每个任务(参见图4):

图4:参与者在一系列任务上调用performAs()方法

任务只是实现Task接口的对象,并且需要实现performAs(actor)方法。 实际上,您可以将任何Task类基本上看作是performAs()方法以及辅助方法的支持类型。

可以使用带注释的字段或构建器来创建任务

为了实现报告的魔力,Serenity BDD需要检测测试中使用的任务和动作对象。 进行此安排的最简单方法是让Serenity像使用任何其他Serenity步骤库一样,使用@Steps批注为您创建它。 在以下代码片段中,Serenity将为您实例化openTheApplication字段,以便James可以使用它来打开应用程序:

@Steps private OpenTheApplication openTheApplication;
…
james.attemptsTo(openTheApplication);

这非常适合非常简单的任务或动作,例如不带参数的任务或动作。 但是对于更复杂的任务或动作,工厂或构建器模式(例如与我们之前的AddATodoItem一起使用的AddATodoItem )更为方便。 经验丰富的从业人员通常喜欢将builder方法和类名结合起来以像英语句子一样阅读,从而使任务的意图保持清晰:

AddATodoItem.called("Buy some milk")

Serenity BDD提供了特殊的Instrumented类,使使用构建器模式轻松创建任务或操作对象成为可能。 例如, AddATodoItem类具有一个不可变的字段,名为thingToDo ,其中包含要在新的Todo项目中使用的文本。

public class AddATodoItem implements Task {

    private final String thingToDo;

    protected AddATodoItem(String thingToDo) { this.thingToDo = thingToDo; }
}

我们可以使用Instrumented.instanceOf().withProperties()方法调用此构造函数,如下所示:

public class AddATodoItem implements Task {

    private final String thingToDo;

    protected AddATodoItem(String thingToDo) { this.thingToDo = thingToDo; }

    public static AddATodoItem called(String thingToDo) {			
        return Instrumented.instanceOf(AddATodoItem.class).
                                                  withProperties(thingToDo);
    }
}

高级别任务由其他低级别任务或动作组成

为了完成工作,高层业务任务通常需要调用较低层的业务任务或与应用程序更直接交互的操作。 实际上,这意味着任务的performAs()方法通常会执行其他较低级别的任务或以其他某种方式与应用程序进行交互。 例如,添加待办事项需要两个UI操作:

  1. 在文本字段中输入待办事项文本
  2. 按回车

早先使用的AddATodoItem类中的performAs()方法确实做到了:

private final String thingToDo;

    @Step("{0} adds a todo item called #thingToDo")
    public <T extends Actor> void performAs(T actor) {			
        actor.attemptsTo(					
                          Enter.theValue(thingToDo)
                              .into(NewTodoForm.NEW_TODO_FIELD)
                              .thenHit(RETURN)
        );
    }

实际的实现使用Enter类,这是Serenity附带的预定义Action类。 动作类与任务类非常相似,不同之处在于它们专注于与应用程序直接交互。 Serenity为核心UI交互提供了一组基本的Action类,例如输入字段值,单击元素或从下拉列表中选择值。 在实践中,这些提供了方便且可读的DSL,可让您描述执行任务所需的常见低级UI交互。

在Serenity Screenplay实现中,我们使用一个特殊的Target类来(默认情况下)使用CSS或XPATH标识元素。 Target对象将WebDriver选择器与显示在测试报告中的人类可读标签相关联,以使报告更具可读性。 您定义一个Target对象,如下所示:

Target WHAT_NEEDS_TO_BE_DONE = Target.the(
                "'What needs to be done?' field").locatedBy("#new-todo")
;

目标通常存储在小型Page-Object之类的类中,这些类负责一件事,知道如何找到特定UI组件的元素,例如此处显示的ToDoList类:

public class ToDoList {
   public static Target WHAT_NEEDS_TO_BE_DONE = Target.the(
        "'What needs to be done?' field").locatedBy("#new-todo");
   public static Target ITEMS = Target.the(
        "List of todo items").locatedBy(".view label");
   public static Target ITEMS_LEFT = Target.the(
        "Count of items left").locatedBy("#todo-count strong");
   public static Target TOGGLE_ALL = Target.the(
        "Toggle all items link").locatedBy("#toggle-all");
   public static Target CLEAR_COMPLETED = Target.the(
        "Clear completed link").locatedBy("#clear-completed");
   public static Target FILTER = Target.the(
        "filter").locatedBy("//*[@id='filters']//a[.='{0}']");
   public static Target SELECTED_FILTER = Target.the(
        "selected filter").locatedBy("#filters li .selected");
}

performAs()方法上的@Step批注用于提供有关任务将如何在测试报告中显示的信息:

@Step("{0} adds a todo item called #thingToDo")
    public <T extends Actor> void performAs(T actor) {…}

任何成员变量都可以使用哈希('#')前缀在@Step批注中通过名称进行引用(例如示例中的“ #thingToDo”)。 您也可以使用特殊的“ {0}”占位符来引用参与者本身。 最终结果是逐项说明如何执行每个业务任务(请参见图5)。

图5:测试报告显示有关任务和UI交互的详细信息

任务可以被其他任务用作构建基块

在其他更高级别的任务中重用任务很容易。 例如,示例项目使用AddTodoItems任务将许多todo添加到列表中,如下所示:

givenThat(james).wasAbleTo(AddTodoItems.called("Walk the dog", 
                                               "Put out the garbage"));

使用AddATodoItem类定义此任务,如下所示:

public class AddTodoItems implements Task {

   private final List<String> todos;

   protected AddTodoItems(List<String> items) { 
       this.todos = ImmutableList.copyOf(items); }

   @Step("{0} adds the todo items called #todos")
   public <T extends Actor> void performAs(T actor) {
       todos.forEach(
               todo -> actor.attemptsTo(
                   AddATodoItem.called(todo)
               )
       );
   }

   public static AddTodoItems called(String... items) {
       return Instrumented.instanceOf(AddTodoItems.class).
                              withProperties(asList(items));
   }
}

以这种方式重用现有任务以构建更复杂的业务任务是很常见的。 我们发现有用的约定是打破通用的Java习惯用法,并将静态创建方法置于performAs()方法之下。 这是因为任务中最有价值的信息是执行方式而不是创建方式。

参与者可以询问有关应用程序状态的问题

典型的自动验收测试包括三个部分:

  1. 设置一些测试数据和/或使应用程序进入已知状态
  2. 执行一些动作
  3. 将新的应用程序状态与预期的状态进行比较。

从测试的角度来看,第三步是真正的价值所在–在这里,我们检查应用程序是否完成了应做的事情。

在传统的Serenity测试中,我们将使用Hamcrest或AssertJ之类的库编写一个断言,以根据预期值检查结果。 使用Serenity Screenplay实现,我们使用一种灵活,流畅的API来表达断言,该API与用于“任务和动作”的API非常相似。 在上面显示的测试中,断言如下所示:

then(james).should(seeThat(TheItems.displayed(), hasItem("Buy some milk")));

该代码的结构如图6所示。

图6:一个宁静的剧本断言

如您所料,此代码将对照预期值(由Hamcrest表达式描述)检查从应用程序(屏幕上显示的项目)中检索到的值。 但是,我们没有传递实际值,而是传递了Question对象。 Question对象的作用是从参与者的角度回答有关应用程序状态的精确问题,通常使用参与者的能力来回答。

问题以易于阅读的形式呈现在报告中

关于Screenplay断言的另一个好处是,它们以非常易读的形式出现在测试报告中,从而使测试的意图更加清晰,并且错误诊断更加容易。 (请参见图8)。

图8:问题对象以易于理解的形式呈现在测试报告中

参与者利用自己的能力与系统进行交互

让我们在另一个测试中看到这个原理。 Todo应用程序的左下角有一个计数器,指示剩余的项目数(请参见图7)。

图7:剩余项目数显示在列表的左下角

描述和验证此行为的测试如下所示:

@Test
public void should_see_the_number_of_todos_decrease_when_an_item_is_completed() 
{

   givenThat(james).wasAbleTo(Start.withATodoListContaining(
                                      "Walk the dog", "Put out the garbage"));

   when(james).attemptsTo(
       CompleteItem.called("Walk the dog")
   );

   then(james).should(seeThat(TheItems.leftCount(), is(1)));
}

该测试需要检查剩余项的数量(如“ item left”计数器所示)为1。相应的断言位于测试的最后一行:

then(james).should(seeThat(TheItems.leftCount(), is(1)));

静态的TheItems.leftCount()方法是一个简单的工厂方法,它返回ItemsLeftCounter类的新实例,如下所示:

public class TheItems {
   public static Question<List<String>> displayed() {
       return new DisplayedItems();
   }

   public static Question<Integer> leftToDoCount() {
       return new ItemsLeftCounter();
   }
}

这仅仅是为了使代码以流畅的方式阅读。

Question对象由ItemsLeftCounter类定义。 此类的职责非常明确:读取待办事项列表底部显示的剩余项目计数文本中的编号。

问题对象类似于任务和操作对象。 但是,不是用于任务和操作的performAs() ,而是Question类需要实现answeredBy (actor)方法,并返回指定类型的结果。 ItemsLeftCounter配置为返回一个整数。

public class ItemsLeftCounter implements Question<Integer> {
   @Override
   public Integer answeredBy(Actor actor) {
       return Text.of(TodoCounter.ITEM_COUNT)
                  .viewedBy(actor)
                  .asInteger();
   }
}

Serenity Screenplay实现提供了许多底层UI交互类,使您可以以声明方式查询网页。 在上面的代码中, answeredBy()方法使用Text交互类检索剩余项计数的text并将其转换为整数。

如前所示,位置逻辑已重构为TodoList类:

public static Target ITEMS_LEFT = Target.the("Count of items left").
                                          locatedBy("#todo-count strong");

该代码再次在三个级别上工作,每个级别都有不同的职责:

  • 顶层步骤对应用程序的状态进行断言:
    then ( james ).should( seeThat (TheItems. leftCount (), is (1)));
  • ItemsLeftCounter Question类查询应用程序的状态,并以断言所期望的形式提供结果;
  • TodoList类存储Question类使用的Web元素的位置。

编写自定义UI交互

Serenity Screenplay实现带有一系列底层UI交互类。 在极少数情况下,这些不能满足您的需求。 在这种情况下,可以直接与WebDriver API进行交互。 您可以通过编写自己的Action类来做到这一点,这很容易做到。

例如,假设我们要使用以下几行代码删除待办事项列表中的一项:

when(james).attemptsTo(
       DeleteAnItem.called("Walk the dog")
);

现在,由于与应用程序实现有关的原因,“删除”按钮不接受WebDriver的常规单击,我们需要直接调用JavaScript事件。 您可以在示例代码中看到完整的类,但是DeleteAnItem任务的performAs()方法使用名为JSClick的自定义Action类来触发JavaScript事件:

@Step("{0} deletes the item '#itemName'")
   public <T extends Actor> void performAs(T theActor) {
       Target deleteButton = TodoListItem.DELETE_ITEM_BUTTON.of(itemName);
       theActor.attemptsTo(JSClick.on(deleteButton));
   }

JSClick类是Action接口的简单实现,如下所示:

public class JSClick implements Action {

   private final Target target;

   @Override
   @Step("{0} clicks on #target")
   public <T extends Actor> void performAs(T theActor) {
       WebElement targetElement = target.resolveFor(theActor);
       BrowseTheWeb.as(theActor).evaluateJavascript(
                                   "arguments[0].click()", targetElement);
   }

   public static Action on(Target target) {
       return instrumented(JSClick.class, target);
   }
  public JSClick(Target target) {
       this.target = target;
   }


}

这里重要的代码在performAs()方法中,在这里我们使用BrowseTheWeb类访问BrowseTheWeb使用浏览器的Ability 。 这样就可以完全访问Serenity WebDriver AP I:

BrowseTheWeb.as(theActor).
           evaluateJavascript("arguments[0].click()", targetElement);

(请注意,这是一个人为的示例– Serenity已经提供了一个交互类,也可以将Javascript注入页面中)。

页面对象变得更小,更专业

使用剧本模式的一个有趣结果是,它改变了您使用和思考页面对象的方式。 Page Object的想法是将与UI相关的逻辑封装为访问或查询网页或网页上的组件,并将其隐藏在对业务更友好的API后面。 从概念上讲,这很好。

但是Page Objects(以及传统的Serenity步骤库)的问题在于,很难使它们井井有条。 随着测试套件的增长,它们趋于增长,变得越来越大,难以维护。 这并不奇怪,因为此类页面对象违反了单一职责原则(SRP)和开放式封闭原则(OCP)– SOLID中的“ S”和“ O”。 许多测试套件最终都具有页面对象的复杂层次结构,它们继承了“常见”行为,例如从父页面对象继承菜单栏或注销按钮,这违反了偏重于继承而不是继承的原则。 新的测试通常需要对现有的Page Object类进行修改,从而带来错误的风险。

当您使用“剧本模式”时,您的页面对象往往会变得更小,更集中,并明确定义了在屏幕上为特定组件定位元素的任务。 写入后,除非基础Web界面发生更改,否则它们通常保持不变。

BDD样式方案不是强制性的

有些人在xUnit框架中编写验收测试,可能不喜欢“ Given / When / Then”风格的编写方案。 这些方法纯粹是为了提高可读性,通过表达您安排(给定),行动(何时)和主张(然后)的位置来使您的意图明确。 并非每个人都喜欢这种风格,因此您并不局限于此。 或者,您可以编写:

james.wasAbleTo(Start.withAnEmptyTodoList());

        james.attemptsTo(AddATodoItem.called("Buy some milk"));

        james.should(seeThat(toDoItems, hasItem("Buy some milk")));

该意图在'wasAbleTo','attemptsTo'和'should'方法中是隐含的,但是我们认为,将意图明确化将使我们以及以后阅读我们代码的其他人受益,因此我们建议使用内置的namedThat (),when(),then()方法。 如果在Cucumber中使用此方法,则可以省去Given / When / Then方法,因为其意图通常在Cucumber步骤定义中是明确的。

结论

剧本模式是一种基于良好的软件工程原理编写自动验收测试的方法,可以使编写干净,可读,可伸缩和高度可维护的测试代码变得更加容易。 这是将页面对象模式无情地重构为SOLID原则的一种可能结果。 Serenity BDD中对剧本模式的新支持带来了许多令人兴奋的可能性。 特别是:

  • Screenplay模式所鼓励的声明式写作风格使编写易于理解和维护的代码变得更加简单。
  • 任务,动作和问题类比传统的Serenity步骤方法更具灵活性,可重用性和可读性。
  • 分离演员的能力会增加很大的灵活性。 例如,使用不同的浏览器实例与多个参与者编写测试非常容易。

像许多好的软件开发实践一样,“剧本模式”也需要一定的纪律。 最初需要格外小心,以设计一个由井井有条的任务,动作和问题组成的,类似于DSL的可读API。 但是,当测试套件扩展时,其好处很快就会显现出来,这些可重用组件库有助于以可持续的速度加快测试编写过程,从而减少通常与自动测试套件的持续维护相关的摩擦。

进一步阅读

这只是对剧本模式及其在Serenity中的实现的介绍。 了解更多信息的最佳方法是学习工作代码。 示例项目的源代码可以在Github上找到。

参考资料

翻译自: https://www.infoq.com/articles/Beyond-Page-Objects-Test-Automation-Serenity-Screenplay/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

js 获取dom离页面编剧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值