silverstripe/behat-extension

SilverStripe 框架 Behat 扩展

安装量:388,760

依赖者: 5

建议者: 0

安全: 0

星标: 31

关注者: 15

分支: 35

公开问题: 9

类型:behat-extension

5.4.0 2024-07-24 21:33 UTC

README

CI Silverstripe supported module

概述

Behat 是一个行为驱动开发的测试框架。因为它主要通过浏览器与您的网站进行交互,所以您不需要任何特定的集成工具就可以与基本的 Silverstripe 网站一起使用,只需按照 标准的 Behat 使用说明 进行操作。

如果您想超越与现有网站和数据库的交互,例如更改数据库内容,这些内容稍后需要回滚到“干净的状态”,则此扩展非常有用。

它提供了以下帮助

  • 在 Behat 上下文中提供对 Silverstripe 类的访问
  • 自动设置临时数据库
  • 在每个场景中重置数据库内容
  • 为 SilverStripe 的登录表单和其他常见任务预建上下文
  • 创建具有预定义权限的成员固定值
  • 在 Behat 场景中创建 YML 固定值定义
  • 等待 jQuery Ajax 响应(而不是固定的等待时间)
  • 捕获 JavaScript 错误并通过 Selenium 记录日志
  • 当检测到断言错误时,将截图保存到文件系统

为了实现这一点,该扩展做出了一个基本假设:您的 Behat 测试与测试的 Silverstripe 代码库相同的同一个应用程序运行,在一个本地托管网站上,来自相同的代码库。这很重要,因为我们需要访问底层的 SilverStripe PHP 类。当然,您可以使用远程浏览器进行实际测试。

注意:该扩展仅与 selenium2 Mink 驱动程序进行了测试。

安装

在 Silverstripe CMS 项目中(请参阅 入门文档),通过 Composer 添加 Silverstripe Behat 扩展。

composer require --dev silverstripe/behat-extension

下载独立的 Google Chrome WebDriver

除非您已设置 SS_BASE_URL,否则您还需要指定您网站根目录的 URL。您可以将它添加到项目根目录中现有的 behat.yml 配置文件,或在终端会话中将它设置为环境变量。

export BEHAT_PARAMS="extensions[SilverStripe\BehatExtension\MinkExtension][base_url]=http://localhost/"

使用

启动 ChromeDriver

您可以在另一个终端会话中本地运行服务器

chromedriver

运行测试

现在您可以运行测试(例如,针对 framework 模块)

vendor/bin/behat @framework

或者通过名称运行单个场景(支持正则表达式)

vendor/bin/behat --name 'My scenario title' @framework

这将默认启动 Chrome 浏览器。其他浏览器和配置可以在 behat.yml 中进行配置。

使用独立命令运行(需要 Bash)

如果您使用 silverstripe/servechromedriver 运行,您还可以使用以下命令,这些命令将自动为单个测试启动和停止这些服务。

vendor/bin/behat-ss @framework

这将自动化

  • 启动服务器
  • 启动 chromedriver
  • 运行 behat
  • 关闭 chromedriver
  • 关闭服务器

请确保在 .env 中将 SS_BASE_URL 设置为 http://localhost:8080

教程

配置

Silverstripe安装程序已经包含一个YML配置文件,该文件可以用于在位于项目根目录的独立ChromeDriver服务器上运行测试,文件名为behat.yml

请确保您已在.env文件中配置了SS_BASE_URL

通用的Mink配置设置位于SilverStripe\BehatExtension\MinkExtension,它是Behat\MinkExtension\Extension的子类。

设置概述(所有都在extensions.SilverStripe\BehatExtension\Extension路径下)

  • ajax_steps:由于Silverstripe广泛使用AJAX请求,我们必须想出一个更有效、更简洁的方法来处理它们,而不是仅仅使用可选的ajax_steps。在这里定义的步骤通过特殊的AJAX处理器进行匹配,以便它们可以被“捕获”,从而调整延迟。您可以使用管道分隔的字符串或匹配步骤定义的子字符串列表。
  • ajax_timeout:在多少毫秒后,Ajax请求被视为超时,并且脚本继续执行断言以避免死锁(默认值:5000)。
  • screenshot_path:用于存储失败步骤最后已知状态的截图的绝对路径。该目录中的截图名称由特征文件名和失败行号组成。

示例:behat.yml

default:
    suites:
    framework:
        paths:
        - '%paths.modules.framework%/tests/behat/features'
        contexts:
        - SilverStripe\Framework\Tests\Behaviour\FeatureContext
        - SilverStripe\Framework\Tests\Behaviour\CmsFormsContext
        - SilverStripe\Framework\Tests\Behaviour\CmsUiContext
        - SilverStripe\BehatExtension\Context\BasicContext
        - SilverStripe\BehatExtension\Context\EmailContext
        - SilverStripe\BehatExtension\Context\LoginContext
        -
            SilverStripe\BehatExtension\Context\FixtureContext:
            - '%paths.modules.framework%/tests/behat/features/files/'
    extensions:
    SilverStripe\BehatExtension\MinkExtension:
        default_session: facebook_web_driver
        javascript_session: facebook_web_driver
        facebook_web_driver:
        browser: chrome
        wd_host: "http://127.0.0.1:9515" #chromedriver port
    SilverStripe\BehatExtension\Extension:
        screenshot_path: '%paths.base%/artifacts/screenshots'

模块初始化

现在您可以开始编写功能了!只需在任何代码库中创建*.feature文件,并像上面那样运行它们。我们建议使用tests/behat/features的文件夹结构,因为它与SilverStripe的PHPUnit测试的常见位置保持一致。

Behat测试依赖于包含步骤定义的FeatureContext类,并且可以由其他子上下文组成,例如用于SilverStripe特定CMS步骤(有关详细信息,请参阅behat.org)。由于步骤定义非常特定于领域,您可能需要自己的上下文。Silverstripe Behat扩展提供了一个初始化脚本,该脚本在推荐的文件夹结构中生成模板。

vendor/bin/behat --init @mymodule --namespace="MyVendor\MyModule"

注意:命名空间是必需的

现在您将有一个位于mymodule/tests/behat/src/FeatureContext.php的类,默认情况下,它将由composer.json添加psr-4类映射。同时,还将创建一个包含功能的文件夹,路径为mymodule/tests/behat/features。将构建一个mymodule/behat.yml,其中包含以模块命名的默认套件。

可用的步骤定义

该扩展包含几个BehatContext子类,并提供了一些额外的步骤定义。其中一些在一般网站测试中很有用,其他一些则特定于SilverStripe。要找出所有可用的步骤(以及它们定义的文件),请运行以下命令

vendor/bin/behat @mymodule --definitions=i

注意:在Silverstripe的framework模块中还有更多特定的步骤定义,用于与CMS界面交互(请参阅framework/tests/behat/features/bootstrap)。除了动态列表之外,在指南末尾还可以找到可用的步骤的速查表。

测试数据

由于每次测试运行都会创建一个新的数据库,除非您明确定义,否则您不能依赖于现有状态。

数据库默认值

获取默认数据的最简单方法是使用DataObject->requireDefaultRecords()。许多模块已经定义了此方法,例如,blog模块自动在页面树中创建默认的BlogHolder条目。有时这些默认值可能适得其反,因此您需要通过在功能定义的顶部放置@database-defaults标签来“启用”它们。默认值在每个场景之后自动重置。

内联定义

如果您需要更多关于正在创建哪些记录的灵活性和透明度,请使用行内定义语法。以下示例展示了某些语法变体

Feature: Do something with pages
    As an site owner
    I want to manage pages

    Background:
        # Creates a new page without data. Can be accessed later under this identifier
        Given a "page" "Page 1"
        # Uses a custom RegistrationPage type
        And an "error page" "Register"
        # Creates a page with inline properties
        And a "page" "Page 2" with "URLSegment"="page-1" and "Content"="my page 1"
        # Field names can be tabular, and based on DataObject::$field_labels
        And the "page" "Page 3" has the following data
            | Content | <blink> |
            | My Property | foo |
            | My Boolean | bar |
        # Pages are published by default, can be explicitly unpublished
        And the "page" "Page 1" is not published
        # Create a hierarchy, and reference a record created earlier
        And the "page" "Page 1.1" is a child of a "page" "Page 1"
        # Specific page type step
        And a "page" "My Redirect" which redirects to a "page" "Page 1"
        And a "member" "Website User" with "FavouritePage"="=>Page.Page 1"

        @javascript
        Scenario: View a page in the tree
            Given I am logged in with "ADMIN" permissions
            And I go to "/admin/pages"
            Then I should see "Page 1"
  • 固定项(Fixtures)是在您定义的位置创建的。如果您希望在每次场景之前创建固定项,请将它们定义在背景(Background)中。如果您只想在特定场景运行时创建它们,请在那里定义。
  • 固定项在场景之间会被清除。
  • 基本语法适用于所有DataObject子类,但某些特定的表示法,如“未发布”(is not published),需要在类上应用如Hierarchy等扩展。
  • 记录类型、标识符、属性名称和属性值都需要引用。
  • 记录类型(类名)可以使用更自然的表示法(例如,“注册页面”而不是“Registration Page”)。
  • 记录类型支持$singular_name表示法,该表示法也用于在整个CMS中引用类型。记录属性名称也以相同的方式支持$field_labels表示法。
  • 属性值也可以使用=>符号来指示记录之间的关系。表示法为=><classname>.<identifier>。对于has_manymany_many关系,可以使用逗号分隔多个关系。

编写Behat测试

目录结构

作为一个惯例,Silverstripe Behat测试位于您的模块的tests/behat子文件夹中。您可以使用以下命令创建它

mkdir -p mymodule/tests/behat/features/
mkdir -p mymodule/tests/behat/src/

FeatureContext

通用的Behat使用说明也适用于这里。唯一的重大区别是扩展您的自己的FeatureContext的基类:它应该是SilverStripeContext而不是BehatContext

示例:mymodule/tests/behat/src/FeatureContext.php

namespace MyModule\Test\Behaviour;

use SilverStripe\BehatExtension\Context\SilverStripeContext;

class FeatureContext extends SilverStripeContext
{
}

屏幕尺寸

在某些Selenium驱动程序中,您可以通过capabilities定义来定义期望的浏览器窗口大小。然而,Selenium默认情况下不支持此功能,因此我们通过环境变量添加了一个解决方案。

BEHAT_SCREEN_SIZE=320x600 vendor/bin/behat

检查PHP会话

Behat从CLI执行,这反过来会在浏览器中触发Web请求。此浏览器会话与PHP会话信息相关联,例如已登录的用户。在每个请求之后,会话信息作为TestSessionEnvironment的一部分持久化到磁盘,以便与Behat CLI共享。

示例:检索当前登录的成员

use SilverStripe\TestSession\TestsessionEnvironment;

$env = Injector::inst()->get(TestSessionEnvironment::class);
$state = $env->getState();

if (isset($state->session['loggedInAs'])) {
    $member = \Member::get()->byID($state->session['loggedInAs']);
} else {
    $member = null;
}

常见问题解答

未找到FeatureContext

这很可能是Composer的自动加载生成器问题。请确保在vendor/composer/autoload_classmap.php文件中提到了“SilverStripe”,如果没有,请调用composer dump-autoload

我如何在步骤中等待异步操作?

有时您需要在调用下一个步骤/断言之前等待AJAX请求或CSS动画完成。Mink提供了wait()方法用于此目的 - 只需让执行等待直到JavaScript表达式满足您的标准。将此表达式作为CSS选择器是非常常见的。Behat测试自带了对等待任何挂起的jQuery.ajax()请求的支持,请检查BasicContext->handleAjaxBeforeStep()ajax_steps配置选项。

为什么模块需要知道文件系统上的框架路径?

有时Silverstripe需要知道您网站的URL。当您在Web浏览器中访问您的网站时,这很容易处理,但如果您在命令行中执行脚本,它就没有办法知道了。

模块如何与SS数据库交互?

模块在初始化时创建临时数据库,并在每个场景之前通过使用/dev/tests/setdb TestRunner端点切换到替代数据库会话。

如果需要,它还会用默认记录填充此临时数据库。

可以包含您自己的固定项,进一步解释。

为什么在新安装的情况下测试通过,但在我自己的项目中失败?

因为我们直接测试接口,对查看元素所做的任何更改都有可能干扰测试。通过从头开始构建测试数据库,我们试图最大限度地减少这种影响。尽管如此,以下是一些可能出现问题的例子:

  • 第三方 Silverstripe 模块,会安装默认数据
  • 默认界面语言的更改
  • 移除管理区域或特定字段的配置

目前还没有方法可以排除测试运行中的违规模块。您必须调整测试以绕过这些更改,或者在没有这些模块的“沙箱”项目中运行测试。

当出现问题时,我该如何调试?

首先,阅读控制台输出。Behat 会告诉您哪些步骤失败了。

Silverstripe 行为测试框架也会通知您一些事件。它试图捕获一些 JavaScript 错误和 AJAX 错误,尽管它限于页面加载后发生的错误。

每当步骤标记为失败时,模块都会捕获截图。请参考上述配置部分以了解如何设置截图路径。

如果您无法使用上述方法收集的信息进行调试,可以通过添加以下步骤来延迟步骤执行:

And I put a breakpoint

这将停止测试的执行,直到您在终端中按下回车键。当您想查看浏览器中的错误或开发者控制台,或者想手动与会话页面进行交互时,这非常有用。

我可以通过 XDebug 设置断点吗?

如果您已经设置了 XDebug,那么断点就是您的朋友。问题是您只能将调试器连接到 CLI 中的 PHP 执行,或者连接到浏览器,但不能同时连接。

首先,确保将 xdebug.remote_autostart 设置为 Off,否则您将始终在 CLI 中有一个活动的调试会话,而不是在浏览器中。

然后您可以选择为当前的 CLI 运行启用 XDebug

XDEBUG_CONFIG="idekey=macgdbp" vendor/bin/behat

或者您可以使用 TESTSESSION_PARAMS 环境变量将额外的参数传递给 dev/testsession/start,并在浏览器中调试。

TESTSESSION_PARAMS="XDEBUG_SESSION_START=macgdbp" vendor/bin/behat @app

macgdbp IDE 密钥需要与您的 php.ini 设置中的 xdebug.idekey 相匹配。

我如何通过 Travis 设置持续集成?

请查看 silverstripe/framework 中的 travis.yml,以了解如何通过 travis-ci.org 设置 Behat 测试的示例。

速查表

这是一个手动分类的列表,列出了安装了 cmsframework 模块时可以使用的可用命令。它基于 vendor/bin/behat -di @cms 的输出。

基础知识

	 Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)"$/
	    - Checks, that page contains specified text.

	 Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)"$/
	    - Checks, that page doesn't contain specified text.

	 Then /^(?:|I )should see text matching (?P<pattern>"(?:[^"]|\\")*")$/
	    - Checks, that page contains text matching specified pattern.

	 Then /^(?:|I )should not see text matching (?P<pattern>"(?:[^"]|\\")*")$/
	    - Checks, that page doesn't contain text matching specified pattern.

	 Then /^the response should contain "(?P<text>(?:[^"]|\\")*)"$/
	    - Checks, that HTML response contains specified string.

	 Then /^the response should not contain "(?P<text>(?:[^"]|\\")*)"$/
	    - Checks, that HTML response doesn't contain specified string.

	 Then /^(?:|I )should see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
	    - Checks, that element with specified CSS contains specified text.

	 Then /^(?:|I )should not see "(?P<text>(?:[^"]|\\")*)" in the "(?P<element>[^"]*)" element$/
	    - Checks, that element with specified CSS doesn't contain specified text.

	 Then /^the "(?P<element>[^"]*)" element should contain "(?P<value>(?:[^"]|\\")*)"$/
	    - Checks, that element with specified CSS contains specified HTML.

	 Then /^(?:|I )should see an? "(?P<element>[^"]*)" element$/
	    - Checks, that element with specified CSS exists on page.

	 Then /^(?:|I )should not see an? "(?P<element>[^"]*)" element$/
	    - Checks, that element with specified CSS doesn't exist on page.

	 Then /^(?:|I )should be on "(?P<page>[^"]+)"$/
	    - Checks, that current page PATH is equal to specified.

	 Then /^the (?i)url(?-i) should match (?P<pattern>"([^"]|\\")*")$/
	    - Checks, that current page PATH matches regular expression.

	 Then /^the response status code should be (?P<code>\d+)$/
	    - Checks, that current page response status is equal to specified.

	 Then /^the response status code should not be (?P<code>\d+)$/
	    - Checks, that current page response status is not equal to specified.

	 Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
	    - Checks, that (?P<num>\d+) CSS elements exist on the page

	 Then /^print last response$/
	    - Prints last response to console.

	 Then /^show last response$/
	    - Opens last response content in browser.

	 Then /^I should be redirected to "([^"]+)"/

	Given /^I wait (?:for )?([\d\.]+) second(?:s?)$/

	Then /^the "([^"]*)" table should contain "([^"]*)"$/

	Then /^the "([^"]*)" table should not contain "([^"]*)"$/

	Given /^I click on "([^"]*)" in the "([^"]*)" table$/

导航

	Given /^(?:|I )am on homepage$/
	    - Opens homepage.

	 When /^(?:|I )go to homepage$/
	    - Opens homepage.

	Given /^(?:|I )am on "(?P<page>[^"]+)"$/
	    - Opens specified page.

	 When /^(?:|I )go to "(?P<page>[^"]+)"$/
	    - Opens specified page.

	 When /^(?:|I )reload the page$/
	    - Reloads current page.

	 When /^(?:|I )move backward one page$/
	    - Moves backward one page in history.

	 When /^(?:|I )move forward one page$/
	    - Moves forward one page in history

表单

	When /^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/
	    - Presses button with specified id|name|title|alt|value.

	 When /^(?:|I )follow "(?P<link>(?:[^"]|\\")*)"$/
	    - Clicks link with specified id|title|alt|text.

	 When /^(?:|I )fill in "(?P<field>(?:[^"]|\\")*)" with "(?P<value>(?:[^"]|\\")*)"$/
	    - Fills in form field with specified id|name|label|value.

	 When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)"$/
	    - Fills in form field with specified id|name|label|value.

	 When /^(?:|I )fill in the following:$/
	    - Fills in form fields with provided table.

	 When /^(?:|I )select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
	    - Selects option in select field with specified id|name|label|value.

	 When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
		- Selects additional option in select field with specified id|name|label|value.

	 When /^I select the "([^"]*)" radio button$/
		- Selects a radio button with the given id|name|label|value

	 When /^(?:|I )check "(?P<option>(?:[^"]|\\")*)"$/
	    - Checks checkbox with specified id|name|label|value.

	 When /^(?:|I )uncheck "(?P<option>(?:[^"]|\\")*)"$/
	    - Unchecks checkbox with specified id|name|label|value.

	 When /^(?:|I )attach the file "(?P[^"]*)" to "(?P<field>(?:[^"]|\\")*)"$/
	    - Attaches file to field with specified id|name|label|value.

	 Then /^the "(?P<field>(?:[^"]|\\")*)" field should contain "(?P<value>(?:[^"]|\\")*)"$/
	    - Checks, that form field with specified id|name|label|value has specified value.

	 Then /^the "(?P<field>(?:[^"]|\\")*)" field should not contain "(?P<value>(?:[^"]|\\")*)"$/
	    - Checks, that form field with specified id|name|label|value doesn't have specified value.

	 Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should be checked$/
	    - Checks, that checkbox with specified in|name|label|value is checked.

	 Then /^the "(?P<checkbox>(?:[^"]|\\")*)" checkbox should not be checked$/
	    - Checks, that checkbox with specified in|name|label|value is unchecked.

	 When /^I fill in the "(?P<field>([^"]*))" HTML field with "(?P<value>([^"]*))"$/

	 When /^I fill in "(?P<value>([^"]*))" for the "(?P<field>([^"]*))" HTML field$/

	 When /^I append "(?P<value>([^"]*))" to the "(?P<field>([^"]*))" HTML field$/

	 Then /^the "(?P<locator>([^"]*))" HTML field should contain "(?P<html>([^"]*))"$/

	When /^(?:|I )fill in the "(?P<field>(?:[^"]|\\")*)" dropdown with "(?P<value>(?:[^"]|\\")*)"$/
	  - Workaround for chosen.js dropdowns or tree dropdowns which hide the original dropdown field.

	When /^(?:|I )fill in "(?P<value>(?:[^"]|\\")*)" for "(?P<field>(?:[^"]|\\")*)" dropdown$/
	  - Workaround for chosen.js dropdowns or tree dropdowns which hide the original dropdown field.

	Given /^I select "([^"]*)" from "([^"]*)" input group$/
	  - Check an individual input button from a group of inputs
	  - Example: I select "Admins" from "Groups" input group
	   (where "Groups" is the title of the CheckboxSetField or OptionsetField form field)

交互

	Given /^I press the "([^"]*)" button$/

	Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element$/

	Given /^I type "([^"]*)" into the dialog$/

	Given /^I (?:press|follow) the "([^"]*)" (?:button|link), confirming the dialog$/

	Given /^I (?:press|follow) the "([^"]*)" (?:button|link), dismissing the dialog$/

    Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, confirming the dialog$/

    Given /^I (click|double click) "([^"]*)" in the "([^"]*)" element, dismissing the dialog$/

	Given /^I confirm the dialog$/

	Given /^I dismiss the dialog$/

登录

	Given /^I am logged in with "([^"]*)" permissions$/
	    - Creates a member in a group with the correct permissions.

	Given /^I am not logged in$/

	 When /^I log in with "(?<username>[^"]*)" and "(?<password>[^"]*)"$/

	Given /^I should see a log-in form$/

	 Then /^I will see a "bad" log-in message$/

CMS UI

	 Then /^I should see an edit page form$/

	 Then /^I should see the CMS$/

	 Then /^I should see a "([^"]*)" message$/

	Given /^I should see a "([^"]*)" button in CMS Content Toolbar$/

	 When /^I should see "([^"]*)" in CMS Tree$/

	 When /^I should not see "([^"]*)" in CMS Tree$/

	 When /^I expand the "([^"]*)" CMS Panel$/

	 When /^I click the "([^"]*)" CMS tab$/

	 Then /^I can see the preview panel$/

	Given /^the preview contains "([^"]*)"$/

	Given /^the preview does not contain "([^"]*)"$/

测试数据

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)" (:?which )?redirects to (?:(an|a|the) )"(?<targetType>[^"]+)" "(?<targetId>[^"]+)"$/
	    - Find or create a redirector page and link to another existing page.

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)"$/
	    - Example: Given a "page" "Page 1"

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)" with (?<data>.*)$/
	    - Example: Given a "page" "Page 1" with "URLSegment"="page-1" and "Content"="my page 1"

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)" has the following data$/
	    - Example: And the "page" "Page 2" has the following data

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)" is a (?<relation>[^\s]*) of (?:(an|a|the) )"(?<relationType>[^"]+)" "(?<relationId>[^"]+)"/
	    - Example: Given the "page" "Page 1.1" is a child of the "page" "Page1"
	      Note that this change is not published by default

	Given /^(?:(an|a|the) )"(?<type>[^"]+)" "(?<id>[^"]+)" is (?<state>[^"]*)$/
	    - Example: Given the "page" "Page 1" is not published
	    - Example: Given the "page" "Page 1" is published
	    - Example: Given the "page" "Page 1" is deleted

	Given /^there are the following ([^\s]*) records$/
	    - Accepts YAML fixture definitions similar to the ones used in Silverstripe unit testing.

	Given /^(?:(an|a|the) )"member" "(?<id>[^"]+)" belonging to "(?<groupId>[^"]+)"$/
	    - Example: Given a "member" "Admin" belonging to "Admin Group"

	Given /^(?:(an|a|the) )"member" "(?<id>[^"]+)" belonging to "(?<groupId>[^"]+)" with (?<data>.*)$/

	Given /^(?:(an|a|the) )"group" "(?<id>[^"]+)" (?:(with|has)) permissions (?<permissionStr>.*)$/
	    - Example: Given a "group" "Admin" with permissions "Access to 'Pages' section" and "Access to 'Files' section"

	Given /^I assign (?:(an|a|the) )"(?<type>[^"]+)" "(?<value>[^"]+)" to (?:(an|a|the) )"(?<relationType>[^"]+)" "(?<relationId>[^"]+)"$/
	    - Example: I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1"

	Given /^I assign (?:(an|a|the) )"(?<type>[^"]+)" "(?<value>[^"]+)" to (?:(an|a|the) )"(?<relationType>[^"]+)" "(?<relationId>[^"]+)" in the "(?<relationName>[^"]+)" relation$
		- Example: I assign the "TaxonomyTerm" "For customers" to the "Page" "Page1" in the "Terms" relation

	Given /^the CMS settings have the following data$/
		- Example: Given the CMS settings has the following data
		- Note: It only works with the Silverstripe CMS module installed

环境

	Given /^the current date is "([^"]*)"$/
	Given /^the current time is "([^"]*)"$/

电子邮件

	Given /^there should (not |)be an email (to|from) "([^"]*)"$/

	Given /^there should (not |)be an email (to|from) "([^"]*)" titled "([^"]*)"$/

	Given /^the email should (not |)contain "([^"]*)"$/
		- Example: Given the email should contain "Thank you for registering!"

	When /^I click on the "([^"]*)" link in the email (to|from) "([^"]*)"$/

	When /^I click on the "([^"]*)" link in the email (to|from) "([^"]*)" titled "([^"]*)"$/

	When /^I click on the "([^"]*)" link in the email"$/

	Given /^I clear all emails$/

	Then /^the email should (not |)contain the following data:$/
		Example: Then the email should contain the following data:

	Then /^there should (not |)be an email titled "([^"]*)"$/

	Then /^the email should (not |)be sent from "([^"]*)"$/

	Then /^the email should (not |)be sent to "([^"]*)"$/

    When /^I click on the http link "([^"]*)" in the email$/
        - Example: When I click on the http link "http://localhost/changepassword" in the email

转换

Behat 转换 有能力根据其原始值更改步骤参数,例如将匹配 \d 正则表达式的任何参数转换为实际的 PHP 整数。

  • /^(?:(the|a)) time of (?<val>.*)$/:转换与 strtotime() 兼容的相对时间语句。例如:“1小时前的时间”如果当前时间是“23:00:00”,则可能返回“22:00:00”。
  • /^(?:(the|a)) date of (?<val>.*)$/:转换与 strtotime() 兼容的相对日期语句。例如:“2天前的日期”如果当前是2013年10月12日,则可能返回“2013-10-10”。
  • /^(?:(the|a)) datetime of (?<val>.*)$/:将与strtotime()兼容的相对日期和时间语句转换。示例:"2天前的日期时间"如果当前是2013年10月12日,可能会返回"2013-10-10 23:00:00"。

有用资源