13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/HttpCall/HttpCallResult.php:
--------------------------------------------------------------------------------
1 | value = $value;
12 | }
13 |
14 | public function update($value): void
15 | {
16 | $this->value = $value;
17 | }
18 |
19 | public function getValue()
20 | {
21 | return $this->value;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/features/ja/system.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @system
3 | フィーチャ: System feature
4 |
5 | シナリオ: Testing execution
6 | 前提 "ls"を実行する
7 |
8 | シナリオ: Testing execution from the project root
9 | 前提 "bin/behat --help"を実行する
10 |
11 | シナリオ: File creation
12 | もし "tests/fixtures/test"というファイルを下記のテキストで作成する:
13 | """
14 | A new file
15 | """
16 | ならば "tests/fixtures/test"というファイルのテキストを表示する
17 |
--------------------------------------------------------------------------------
/tests/features/pt/debug.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | Funcionalidade: Debug
3 |
4 | @user
5 | Cenário: Testando um breakpoint
6 | Quando Eu estou em "index.html"
7 | E coloco um breakpoint
8 | Então devo ver "Congratulations, you've correctly set up your apache environment."
9 | E coloco um breakpoint
10 |
11 | @javascript
12 | Cenário: Capturando uma screenshot
13 | Quando Eu estou em "index.html"
14 | E salvo uma screenshot em "index.png"
15 |
--------------------------------------------------------------------------------
/tests/features/ja/debug.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @debug
3 | 機能: Debug Feature
4 |
5 | @user
6 | シナリオ: Testing a break point
7 | 前提 "/index.html" を表示している
8 | ならば 私がブレークポイントを設置する
9 | ならば 画面に "Congratulations, you've correctly set up your apache environment." と表示されていること
10 | ならば ブレークポイントを設置する
11 |
12 | @javascript
13 | シナリオ: Taking a screenshot
14 | 前提 "/index.html" を表示している
15 | ならば スクリーンショットを"./index.png"に保存する
16 |
--------------------------------------------------------------------------------
/tests/fixtures/www/json/schemaref.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "object",
3 | "$schema": "http://json-schema.org/draft-03/schema",
4 | "required": true,
5 | "definitions": {
6 | "url": {
7 | "type": "string",
8 | "pattern": "^https?\\:\\/\\/.+\\.[a-zA-Z]{2,4}$"
9 | }
10 | },
11 | "properties": {
12 | "foo": { "$ref": "#/definitions/url"},
13 | "bar": { "$ref": "definitions.json#/definitions/https"}
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/features/fr/debug.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | Fonctionnalité:
3 |
4 | @user
5 | Scénario:
6 | Étant donné je suis sur "index.html"
7 | Alors je pose un point d'arrêt
8 | Et je devrais voir "Congratulations, you've correctly set up your apache environment."
9 | Alors je pose un point d'arrêt
10 |
11 | @javascript
12 | Scénario:
13 | Étant donné je suis sur "index.html"
14 | Et sauvegarde une capture d'écran dans "index.png"
15 |
--------------------------------------------------------------------------------
/tests/features/ru/debug.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | Функционал: Отладка
3 |
4 | @user
5 | Сценарий: Тестирование паузы
6 | Пусть я на странице "index.html"
7 | Тогда я ставлю паузу
8 | Тогда я должен видеть "Congratulations, you've correctly set up your apache environment."
9 | Тогда я ставлю паузу
10 |
11 | @javascript
12 | Сценарий: Снятие скриншота
13 | Пусть я на странице "index.html"
14 | И я сохраняю скриншот в "index.png"
15 |
--------------------------------------------------------------------------------
/src/HttpCall/HttpCallResultPool.php:
--------------------------------------------------------------------------------
1 | result = $result;
15 | }
16 |
17 | /**
18 | * @return HttpCallResult|null
19 | */
20 | public function getResult()
21 | {
22 | return $this->result;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/features/en/debug.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | Feature: Browser Feature
3 |
4 | @user
5 | Scenario: Testing a break point
6 | Given I am on "index.html"
7 | Then I put a breakpoint
8 | Then I should see "Congratulations, you've correctly set up your apache environment."
9 | Then I put a breakpoint
10 |
11 | @javascript
12 | Scenario: Taking a screenshot
13 | Given I am on "index.html"
14 | And I save a screenshot in "index.png"
15 |
--------------------------------------------------------------------------------
/src/HttpCall/RestContextVoter.php:
--------------------------------------------------------------------------------
1 | getValue() instanceof \Behat\Mink\Element\DocumentElement;
10 | }
11 |
12 | public function filter(HttpCallResult $httpCallResult)
13 | {
14 | return $httpCallResult->getValue()->getContent();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tests/fixtures/www/browser/frames.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/schema.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/fixtures/www/table/index.html:
--------------------------------------------------------------------------------
1 | You are about to test table.
2 |
3 |
4 |
5 |
6 |
Lorem
7 |
Ipsum
8 |
Integer
9 |
10 |
11 |
12 |
13 |
Lorem ipsum dolor sit amet
14 |
15 |
16 |
17 |
18 |
Lorem
19 |
Ipsum
20 |
42
21 |
22 |
23 |
Dolor
24 |
Sit
25 |
24
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/tests/fixtures/www/browser/timeout.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Timeout test
4 |
5 |
6 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/people.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rubber Ducky
6 |
7 |
8 |
9 |
10 | Car
11 |
12 |
13 |
14 |
15 | Bowling ball
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/schema.ng:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/country.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Shiba Park
10 |
11 |
12 | Maruyama Park
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/UPGRADE-3.0.md:
--------------------------------------------------------------------------------
1 | UPGRADE FROM 2.x to 3.0
2 | =======================
3 |
4 | * All classes have moved to `Sanpi\Behatch` namespace to `Behatch`.
5 |
6 | * The contexts aliases start with `behatch:context:` instead of `behatch:`
7 | prefix.
8 |
9 | * Fixed miss spelling methods:
10 | * `JsonContext::theJsonNodesShoudBeEqualTo` => `JsonContext::theJsonNodesShouldBeEqualTo`
11 | * `JsonContext::theJsonNodesShoudContain` => `JsonContext::theJsonNodesShouldContain`
12 | * `JsonContext::theJsonNodesShoudNotContain` => `JsonContext::theJsonNodesShouldNotContain`
13 | * `SystemContext::ouputShouldNotContain` => `SystemContext::outputShouldNotContain`
14 |
--------------------------------------------------------------------------------
/src/Context/ContextClass/ClassResolver.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/feed.rss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RSS Title
5 | This is an example of an RSS feed
6 | http://www.someexamplerssdomain.com/main.html
7 | Mon, 06 Sep 2010 00:01:00 +0000
8 | Mon, 06 Sep 2009 16:45:00 +0000
9 | 1800
10 |
11 | Example entry
12 | Here is some text containing an interesting description.
13 | http://www.wikipedia.org/
14 | unique string per item
15 | Mon, 06 Sep 2009 16:45:00 +0000
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tests/fixtures/www/browser/auth.php:
--------------------------------------------------------------------------------
1 | Login";
11 | } elseif (
12 | !isset($_SERVER['PHP_AUTH_USER'])
13 | || !isset($_SERVER['PHP_AUTH_PW'])
14 | || !isset($_SESSION['login'])
15 | ) {
16 | header('WWW-Authenticate: Basic realm="Test"');
17 | header('HTTP/1.0 401 Unauthorized');
18 | $_SESSION['login'] = true;
19 | echo 'NONE SHALL PASS !';
20 | } else {
21 | if (
22 | $_SERVER['PHP_AUTH_USER'] == $username
23 | && $_SERVER['PHP_AUTH_PW'] == $password
24 | ) {
25 | echo 'Successfuly logged in';
26 | } else {
27 | unset($_SESSION['login']);
28 | header('Location: '.$_SERVER['PHP_SELF']);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/HttpCall/ContextSupportedVoters.php:
--------------------------------------------------------------------------------
1 | register($voter);
13 | }
14 | }
15 |
16 | public function register(ContextSupportedVoter $voter): void
17 | {
18 | $this->voters[] = $voter;
19 | }
20 |
21 | public function vote(HttpCallResult $httpCallResult)
22 | {
23 | foreach ($this->voters as $voter) {
24 | if ($voter->vote($httpCallResult)) {
25 | if ($voter instanceof FilterableHttpCallResult) {
26 | $httpCallResult->update($voter->filter($httpCallResult));
27 | }
28 |
29 | return true;
30 | }
31 | }
32 |
33 | return false;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Html.php:
--------------------------------------------------------------------------------
1 | getSession()->getPage();
12 |
13 | $parents = $page->findAll('css', $parent);
14 | if (!isset($parents[$index - 1])) {
15 | throw new \Exception("The $index element '$parent' was not found anywhere in the page");
16 | }
17 |
18 | $elements = $parents[$index - 1]->findAll('css', $element);
19 |
20 | return \count($elements);
21 | }
22 |
23 | protected function findElement($selector, $locator, $index)
24 | {
25 | $page = $this->getSession()->getPage();
26 |
27 | $nodes = $page->findAll($selector, $locator);
28 |
29 | if (!isset($nodes[$index - 1])) {
30 | throw new \Exception("The $index $selector '$locator' was not found anywhere in the page");
31 | }
32 |
33 | return $nodes[$index - 1];
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Behatch is an open source, community-driven project. If you'd like to
4 | contribute, feel free to do this, but remember to follow this few simple rules:
5 |
6 | * Make your feature addition or bug fix,
7 | * __Always__ as base for your changes use `master` branch (all new development
8 | happens here),
9 | * Add `*.features` for those changes (please look into `features/` folder for
10 | some examples). This is important so we don't break it in a future version
11 | unintentionally,
12 | * __Remember__: when you create Pull Request, always select `master` branch as
13 | target, otherwise it will be closed (this is selected by default).
14 |
15 | # Contributing to Formatter Translations
16 |
17 | Almost step provide by Behatch could be translated into your language with
18 | `--lang` option. In order to fix/add translation, edit the appropriate file in
19 | the `i18n` directory.
20 |
21 | # Running tests
22 |
23 | Make sure that you don't break anything with your changes by running the test
24 | suites:
25 |
26 | ```bash
27 | $> ./bin/atoum
28 | $> ./bin/behat
29 | ```
30 |
--------------------------------------------------------------------------------
/tests/fixtures/www/browser/elements.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
1
4 |
1
5 |
6 |
11 |
12 |
13 |
14 |
First
15 |
Second
16 |
17 |
18 |
19 | First
20 | Second
21 |
27 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | php_sim: true
3 | php_pdepend: true
4 | php_analyzer: true
5 |
6 | filter:
7 | excluded_paths:
8 | - 'tests/*'
9 | - 'bin/*'
10 | checks:
11 | php:
12 | excluded_dependencies:
13 | - atoum/atoum
14 | security_vulnerabilities: true
15 | use_self_instead_of_fqcn: true
16 | uppercase_constants: true
17 | simplify_boolean_return: true
18 | remove_extra_empty_lines: true
19 | properties_in_camelcaps: true
20 | prefer_while_loop_over_for_loop: true
21 | parameters_in_camelcaps: true
22 | optional_parameters_at_the_end: true
23 | no_goto: true
24 | newline_at_end_of_file: true
25 | classes_in_camel_caps: true
26 | avoid_todo_comments: true
27 | avoid_multiple_statements_on_same_line: true
28 | avoid_fixme_comments: true
29 | fix_doc_comments: false
30 |
31 | build:
32 | tests:
33 | override:
34 | -
35 | command: './bin/atoum'
36 | coverage:
37 | file: 'atoum.xunit.xml'
38 | format: 'php-clover'
39 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/feed.atom:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Example Feed
6 | A subtitle.
7 |
8 |
9 | urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6
10 | 2003-12-13T18:30:02Z
11 |
12 |
13 |
14 | Atom-Powered Robots Run Amok
15 |
16 |
17 |
18 | urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a
19 | 2003-12-13T18:30:02Z
20 | Some text.
21 |
22 | John Doe
23 | johndoe@example.com
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Resources/services/http_call.yml:
--------------------------------------------------------------------------------
1 | services:
2 | behatch.http_call.listener:
3 | class: Behatch\HttpCall\HttpCallListener
4 | arguments: ["@behatch.context_supported.voter", "@behatch.http_call.result_pool", "@mink"]
5 | public: false
6 | tags:
7 | - { name: event_dispatcher.subscriber, priority: 0 }
8 |
9 | behatch.http_call.result_pool:
10 | class: Behatch\HttpCall\HttpCallResultPool
11 | public: false
12 |
13 | behatch.context_supported.voter:
14 | class: Behatch\HttpCall\ContextSupportedVoters
15 | public: false
16 |
17 | behatch.rest_context_supported.voter:
18 | class: Behatch\HttpCall\RestContextVoter
19 | public: false
20 | tags:
21 | - { name: behatch.context_voter }
22 |
23 | behatch.http_call.request:
24 | class: Behatch\HttpCall\Request
25 | arguments: ["@mink"]
26 | public: false
27 |
28 | behatch.http_call.argument_resolver:
29 | class: Behatch\HttpCall\HttpCallResultPoolResolver
30 | arguments: ["@behatch.http_call.result_pool", "@behatch.http_call.request"]
31 | public: false
32 | tags:
33 | - { name: context.argument_resolver }
34 |
--------------------------------------------------------------------------------
/tests/features/fr/table.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | Fonctionnalité:
3 |
4 | Scénario:
5 | Étant donné je suis sur "/table/index.html"
6 | Alors je devrais voir "You are about to test table."
7 |
8 | Scénario:
9 | Étant donné je suis sur "/table/index.html"
10 |
11 | Alors je devrais voir 3 colonnes dans le tableau "table"
12 |
13 | Et le schéma des colonnes du tableau "table" devrait correspondre à :
14 | | columns |
15 | | Lorem |
16 | | Ipsum |
17 | | Integer |
18 |
19 | Scénario:
20 | Étant donné je suis sur "/table/index.html"
21 |
22 | Alors je devrais voir 2 lignes dans le tableau "table"
23 | Et je devrais voir 2 lignes dans le 1ier tableau "table"
24 |
25 | Et les données dans la 1ière ligne du tableau "table" devraient correspondre à :
26 | | col1 | col2 |
27 | | Lorem | Ipsum |
28 |
29 | Et les données dans la 2ième ligne du tableau "table" devraient correspondre à :
30 | | col1 | col2 |
31 | | Dolor | Sit |
32 |
33 | Scénario:
34 | Étant donné je suis sur "/table/index.html"
35 |
36 | Alors la 1ière colonne de la 1ière ligne du tableau "table" devrait contenir "Lorem"
37 | Et la 2nde colonne de la 1ière ligne du tableau "table" devrait contenir "Ipsum"
38 |
--------------------------------------------------------------------------------
/src/Json/JsonSchema.php:
--------------------------------------------------------------------------------
1 | uri = $uri;
15 |
16 | parent::__construct($content);
17 | }
18 |
19 | public function resolve(SchemaStorage $resolver)
20 | {
21 | if (!$this->hasUri()) {
22 | return $this;
23 | }
24 |
25 | $this->content = $resolver->resolveRef($this->uri);
26 |
27 | return $this;
28 | }
29 |
30 | public function validate(Json $json, Validator $validator)
31 | {
32 | $validator->check($json->getContent(), $this->getContent());
33 |
34 | if (!$validator->isValid()) {
35 | $msg = 'JSON does not validate. Violations:'.\PHP_EOL;
36 | foreach ($validator->getErrors() as $error) {
37 | $msg .= \sprintf(' - [%s] %s'.\PHP_EOL, $error['property'], $error['message']);
38 | }
39 | throw new \Exception($msg);
40 | }
41 |
42 | return true;
43 | }
44 |
45 | private function hasUri()
46 | {
47 | return null !== $this->uri;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Context/BaseContext.php:
--------------------------------------------------------------------------------
1 | setMink($this->getMink());
45 | $context->setMinkParameters($this->getMinkParameters());
46 |
47 | return $context;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/HttpCall/HttpCallResultPoolResolver.php:
--------------------------------------------------------------------------------
1 | dependencies = [];
14 |
15 | foreach (\func_get_args() as $param) {
16 | $this->dependencies[$param::class] = $param;
17 | }
18 | }
19 |
20 | public function resolveArguments(\ReflectionClass $classReflection, array $arguments)
21 | {
22 | if (null !== ($constructor = $classReflection->getConstructor())) {
23 | foreach ($constructor->getParameters() as $parameter) {
24 | if (!($type = $parameter->getType()) instanceof \ReflectionNamedType) {
25 | continue;
26 | }
27 |
28 | if ($type->isBuiltin()) {
29 | continue;
30 | }
31 |
32 | $class = new \ReflectionClass($type->getName());
33 |
34 | if (!isset($this->dependencies[$class->name])) {
35 | continue;
36 | }
37 |
38 | $arguments[$parameter->name] = $this->dependencies[$class->name];
39 | }
40 | }
41 |
42 | return $arguments;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "soyuka/contexts",
3 | "description": "Behatch contexts",
4 | "keywords": ["BDD", "Behat", "Symfony2", "Context"],
5 | "type": "library",
6 | "license": "beerware",
7 |
8 | "require": {
9 | "php": ">=8.0",
10 | "behat/behat": "^3.0.13",
11 | "friends-of-behat/mink-extension": "^2.3.1",
12 | "justinrainbow/json-schema": "^5.0|^6.0",
13 | "symfony/property-access": "^2.3|^3.0|^4.0|^5.0|^6.0|^7.0",
14 | "symfony/http-foundation": "^2.3|^3.0|^4.0|^5.0|^6.0|^7.0",
15 | "symfony/dom-crawler": "^2.4|^3.0|^4.0|^5.0|^6.0|^7.0"
16 | },
17 |
18 | "require-dev": {
19 | "behat/mink-goutte-driver": "^1.1",
20 | "guzzlehttp/guzzle": "^6.3",
21 | "behat/mink-selenium2-driver": "^1.6",
22 | "atoum/atoum": "^4.0",
23 | "fabpot/goutte": "^3.2",
24 | "phpunit/phpunit": "^9.5",
25 | "atoum/stubs": "^2.6"
26 | },
27 |
28 | "autoload": {
29 | "psr-4": {
30 | "Behatch\\": "src/",
31 | "Behatch\\Tests\\Units\\": "tests/units/"
32 | }
33 | },
34 |
35 | "config": {
36 | "bin-dir": "bin/"
37 | },
38 |
39 | "replace": {
40 | "sanpi/behatch-contexts": "self.version"
41 | },
42 |
43 | "extra": {
44 | "branch-alias": {
45 | "dev-master": "3.0.x-dev"
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/features/pt/system.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | Funcionalidade: System
3 |
4 | Cenário: Testando a execução
5 | Quando Eu executo "true"
6 | Então o comando deve ser executado com sucesso
7 | Quando Eu executo "false"
8 | Então o comando deve falhar
9 |
10 | Cenário: Testando o tempo de execução
11 | Quando Eu executo "sleep 1"
12 | Então o comando deve demorar menos que 2 segundos
13 |
14 | Quando Eu executo "sleep 2"
15 | Então o comando deve demorar mais que 1 segundos
16 |
17 | Cenário: Testando a saída da execução
18 | Quando Eu executo "echo 'Hello world'"
19 | Então a saída deve conter "Hello world"
20 | E a saída deve conter "Hel.*ld"
21 | E a saída não deve conter "Hello John"
22 | E a saída não deve conter "Hel.*hn"
23 |
24 | Cenário: Testando a saída da execução com múltiplas linhas
25 | Quando Eu executo "echo 'Hello world\nHow are you?'"
26 | Então a saída deve ser:
27 | """
28 | Hello world
29 | How are you?
30 | """
31 | E a saída não deve ser:
32 | """
33 | Hello John
34 | How are you?
35 | """
36 |
37 | Cenário: Testando a execução de comandos a partir da raiz do projeto
38 | Quando executo "bin/behat --help"
39 |
40 | Cenário: Criação de arquivo
41 | Quando crio o arquivo "tests/fixtures/test" contendo:
42 | """
43 | A new file
44 | """
45 | Então exiba o conteúdo do arquivo "tests/fixtures/test"
46 |
--------------------------------------------------------------------------------
/tests/features/fr/system.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | Fonctionnalité:
3 |
4 | Scénario:
5 | Étant donné j'exécute "true"
6 | Alors la commande devrait réussir
7 | Étant donné j'exécute "false"
8 | Alors la commande planter
9 |
10 | Scénario:
11 | Étant donné j'exécute "sleep 1"
12 | Alors la commande devrait durer moins de 2 secondes
13 |
14 | Étant donné j'exécute "sleep 2"
15 | Alors la commande devrait durer plus de 1 secondes
16 |
17 | Scénario:
18 | Étant donné j'exécute "echo 'Hello world'"
19 | Alors la sortie devrait contenir "Hello world"
20 | Et la sortie devrait contenir "Hel.*ld"
21 | Et la sortie ne devrait pas contenir "Hello John"
22 | Et la sortie ne devrait pas contenir "Hel.*hn"
23 |
24 | Scénario:
25 | Étant donné j'exécute "echo 'Hello world'"
26 | Alors la sortie devrait être égale à :
27 | """
28 | Hello world
29 | How are you?
30 | """
31 | Et la sortie ne devrait pas être égale à :
32 | """
33 | Hello John
34 | How are you?
35 | """
36 |
37 | Scénario:
38 | Étant donné j'exécute "bin/behat --help"
39 |
40 | Scénario: création de fichier
41 | Quand je crée le fichier "tests/fixtures/test" contenant :
42 | """
43 | A new file
44 | """
45 | Alors imprimer le contenu du fichier "tests/fixtures/test"
46 |
--------------------------------------------------------------------------------
/src/HttpCall/Request/Goutte.php:
--------------------------------------------------------------------------------
1 | requestHeaders));
18 | $this->resetHttpHeaders();
19 |
20 | return $page;
21 | }
22 |
23 | public function setHttpHeader($name, $value): void
24 | {
25 | /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
26 | $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
27 | $name = str_replace('-', '_', strtoupper($name));
28 |
29 | // CONTENT_* are not prefixed with HTTP_ in PHP when building $_SERVER
30 | if (!isset($contentHeaders[$name])) {
31 | $name = 'HTTP_'.$name;
32 | }
33 | /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
34 |
35 | $this->requestHeaders[$name] = $value;
36 | }
37 |
38 | protected function resetHttpHeaders(): void
39 | {
40 | $this->requestHeaders = [];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/features/ru/system.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | Функционал: Системные команды
3 |
4 | Сценарий: Тестирование выполнения
5 | Пусть я выполняю "true"
6 | Тогда команда должна выполниться успешно
7 | Пусть я выполняю "false"
8 | Тогда команда должна выполниться неуспешно
9 |
10 | Сценарий: Тестирование времени выполнения
11 | Пусть я выполняю "sleep 1"
12 | Тогда команда должна выполняться менее чем 2 секунды
13 |
14 | Пусть я выполняю "sleep 2"
15 | Тогда команда должна выполняться более чем 1 секунду
16 |
17 | Сценарий: Тестирование вывода
18 | Пусть я выполняю "echo 'Hello world'"
19 | Тогда вывод должен содержать "Hello world"
20 | И вывод должен содержать "Hel.*ld"
21 | И вывод не должен содержать "Hello John"
22 | И вывод не должен содержать "Hel.*hn"
23 |
24 | Сценарий: Тестирование полного вывода
25 | Пусть я выполняю "echo 'Hello world\nHow are you?'"
26 | Тогда вывод должен быть:
27 | """
28 | Hello world
29 | How are you?
30 | """
31 | И вывод не должен быть:
32 | """
33 | Hello John
34 | How are you?
35 | """
36 |
37 | Сценарий: Тестирование выполнения из корня проекта
38 | Пусть я выполняю "bin/behat --help"
39 |
40 | Сценарий: Создание файлов
41 | Когда я создаю файл "tests/fixtures/test" с содержимым:
42 | """
43 | A new file
44 | """
45 | Тогда выведи содержимое файла "tests/fixtures/test"
46 |
--------------------------------------------------------------------------------
/tests/features/ja/table.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @table
3 | フィーチャ: Table Feature
4 |
5 | シナリオ: Testing access to /table/index.html
6 | 前提 "/table/index.html" を表示している
7 | ならば 画面に "You are about to test table." と表示されていること
8 |
9 | シナリオ: Testing columns
10 | 前提 "/table/index.html" を表示している
11 |
12 | ならば テーブル"table"が3個のカラムを持つこと
13 |
14 | かつ テーブル"table"のカラムスキーマが下記と一致すること:
15 | | columns |
16 | | Lorem |
17 | | Ipsum |
18 | | Integer |
19 | # ならば ブレークポイントを設置する
20 |
21 | シナリオ: Testing rows
22 | 前提 "/table/index.html" を表示している
23 |
24 | ならば テーブル"table"が2行持つこと
25 | かつ 1番目のテーブル"table"が2行持つこと
26 |
27 | かつ テーブル"table"の1行目のデータが下記と一致すること:
28 | | col1 | col2 |
29 | | Lorem | Ipsum |
30 |
31 | かつ テーブル"table"の2行目のデータが下記と一致すること:
32 | | col1 | col2 |
33 | | Dolor | Sit |
34 | # ならば ブレークポイントを設置する
35 |
36 | シナリオ: Partial Testing rows
37 | 前提 "/table/index.html" を表示している
38 |
39 | ならば テーブル"table"が2行持つこと
40 | かつ 1番目のテーブル"table"が2行持つこと
41 |
42 | かつ テーブル"table"の1行目のデータが下記と一致すること:
43 | | col2 |
44 | | Ipsum |
45 |
46 | かつ テーブル"table"の2行目のデータが下記と一致すること:
47 | | col1 |
48 | | Dolor |
49 | # ならば ブレークポイントを設置する
50 |
51 | シナリオ: Testing cell content
52 | 前提 "/table/index.html" を表示している
53 | ならば テーブル"table"の1行目1列が"Lorem"を含むこと
54 |
55 | かつ テーブル"table"の1行目2列が"Ipsum"を含むこと
56 |
--------------------------------------------------------------------------------
/tests/features/pt/table.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | Funcionalidade: Table
3 |
4 | Cenário: Testando o acesso a /table/index.html
5 | Quando estou em "/table/index.html"
6 | Então devo ver "You are about to test table."
7 |
8 | Cenário: Testando colunas
9 | Quando estou em "/table/index.html"
10 |
11 | Então devo ver 3 colunas na tabela "table"
12 |
13 | E as colunas da tabela "table" devem ser:
14 | | columns |
15 | | Lorem |
16 | | Ipsum |
17 | | Integer |
18 |
19 | Cenário: Testando linhas
20 | Quando estou em "/table/index.html"
21 |
22 | Então devo ver 2 linhas na tabela "table"
23 | E devo ver 2 linhas na 1ª tabela "table"
24 |
25 | E os dados na 1ª linha da tabela "table" devem ser iguais a:
26 | | col1 | col2 |
27 | | Lorem | Ipsum |
28 |
29 | E os dados na 2ª linha da tabela "table" devem ser iguais a:
30 | | col1 | col2 |
31 | | Dolor | Sit |
32 |
33 | Cenário: Teste parcial de linhas
34 | Quando estou em "/table/index.html"
35 |
36 | Então devo ver 2 linhas na tabela "table"
37 | E devo ver 2 linhas na 1ª tabela "table"
38 |
39 | E os dados na 1ª linha da tabela "table" devem ser iguais a:
40 | | col2 |
41 | | Ipsum |
42 |
43 | E os dados na 2ª linha da tabela "table" devem ser iguais a:
44 | | col1 |
45 | | Dolor |
46 |
47 | Cenário: Testando o conteúdo das células
48 | Quando estou em "/table/index.html"
49 | Então a 1ª coluna da 1ª linha da tabela "table" deve conter "Lorem"
50 | E a 2ª coluna da 1ª linha da tabela "table" deve conter "Ipsum"
51 |
--------------------------------------------------------------------------------
/tests/features/en/system.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | Feature: System feature
3 |
4 | Scenario: Testing execution
5 | Given I execute "true"
6 | Then command should succeed
7 | Given I execute "false"
8 | Then command should fail
9 |
10 | Scenario: Testing execution time
11 | Given I execute "sleep 1"
12 | Then Command should last less than 2 seconds
13 |
14 | Given I execute "sleep 2"
15 | Then Command should last more than 1 seconds
16 |
17 | Scenario: Testing displaying output
18 | Given I execute "echo 'Hello world'"
19 | Then display the last command output
20 |
21 | Scenario: Testing execution output
22 | Given I execute "echo 'Hello world'"
23 | Then output should contain "Hello world"
24 | And output should contain "Hel.*ld"
25 | And output should not contain "Hello John"
26 | And output should not contain "Hel.*hn"
27 |
28 | Scenario: Testing execution output wall output
29 | Given I execute "echo 'Hello world\nHow are you?'"
30 | Then output should be:
31 | """
32 | Hello world
33 | How are you?
34 | """
35 | And output should not be:
36 | """
37 | Hello John
38 | How are you?
39 | """
40 |
41 | Scenario: Testing execution from the project root
42 | Given I execute "bin/behat --help"
43 |
44 | Scenario: File creation
45 | When I create the file "tests/fixtures/test" containing:
46 | """
47 | A new file
48 | """
49 | Then print the content of "tests/fixtures/test" file
50 |
--------------------------------------------------------------------------------
/tests/fixtures/www/rest/index.php:
--------------------------------------------------------------------------------
1 |
17 |
18 | You have sent a request.
19 |
20 | header(s) received.
21 | $value) { ?>
22 | :
23 |
24 |
25 |
26 | No parameter received.
27 |
28 | parameter(s) received.
29 | $value) { ?>
30 | :
31 |
32 |
33 |
34 |
35 | No files received.
36 |
37 | file(s) received.
38 | $value) { ?>
39 | - name :
40 | - error :
41 | - size :
42 |
43 |
44 |
45 |
46 | No body received.
47 |
48 | Body :
49 |
50 |
--------------------------------------------------------------------------------
/src/Json/JsonInspector.php:
--------------------------------------------------------------------------------
1 | evaluationMode = $evaluationMode;
23 | $this->accessor = new PropertyAccessor($magicMethods, $throwException);
24 | }
25 |
26 | public function evaluate(Json $json, $expression)
27 | {
28 | if ('javascript' === $this->evaluationMode) {
29 | $expression = str_replace('->', '.', $expression);
30 | }
31 |
32 | try {
33 | return $json->read($expression, $this->accessor);
34 | } catch (\Exception $e) {
35 | throw new \Exception("Failed to evaluate expression '$expression'");
36 | }
37 | }
38 |
39 | public function validate(Json $json, JsonSchema $schema)
40 | {
41 | $validator = new \JsonSchema\Validator();
42 |
43 | $resolver = new \JsonSchema\SchemaStorage(new \JsonSchema\Uri\UriRetriever(), new \JsonSchema\Uri\UriResolver());
44 | $schema->resolve($resolver);
45 |
46 | return $schema->validate($json, $validator);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/fixtures/www/json/swagger.json:
--------------------------------------------------------------------------------
1 | {
2 | "swagger": "2.0",
3 | "basePath": "\/",
4 | "info": {
5 | "title": "Sample API",
6 | "version": "2.0.0",
7 | "description": "Sample API"
8 | },
9 | "paths": {
10 | "\/a\/path": {
11 | "get": {
12 | "tags": [
13 | "sample"
14 | ],
15 | "operationId": "sampleId",
16 | "produces": [
17 | "application\/json",
18 | "text\/html"
19 | ],
20 | "summary": "Just a fixture sample endpoint",
21 | "responses": {},
22 | "parameters": []
23 | }
24 | }
25 | },
26 | "definitions": {
27 | "sample-invalid-definition": {
28 | "type": "object",
29 | "description": "",
30 | "properties": {
31 | "stringValue": {
32 | "readOnly": true,
33 | "type": "integer"
34 | },
35 | "intValue": {
36 | "readOnly": true,
37 | "type": "integer"
38 | }
39 | }
40 | },
41 | "sample-definition": {
42 | "type": "object",
43 | "description": "",
44 | "properties": {
45 | "stringValue": {
46 | "readOnly": true,
47 | "type": "string"
48 | },
49 | "intValue": {
50 | "readOnly": true,
51 | "type": "integer"
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/units/Json/Json.php:
--------------------------------------------------------------------------------
1 | newTestedInstance('{"foo": "bar"}');
12 | $this->object($json)
13 | ->isInstanceOf('Behatch\Json\Json');
14 | }
15 |
16 | public function test_construct_invalid_json(): void
17 | {
18 | $this->exception(function (): void {
19 | $json = $this->newTestedInstance('{{json');
20 | })
21 | ->hasMessage("The string '{{json' is not valid json");
22 | }
23 |
24 | public function test_to_string(): void
25 | {
26 | $content = '{"foo":"bar"}';
27 | $json = $this->newTestedInstance($content);
28 |
29 | $this->castToString($json)
30 | ->isEqualTo($content);
31 | }
32 |
33 | public function test_read(): void
34 | {
35 | $accessor = PropertyAccess::createPropertyAccessor();
36 | $json = $this->newTestedInstance('{"foo":"bar"}');
37 | $result = $json->read('foo', $accessor);
38 |
39 | $this->string($result)
40 | ->isEqualTo('bar');
41 | }
42 |
43 | public function test_read_invalid_expression(): void
44 | {
45 | $accessor = PropertyAccess::createPropertyAccessor();
46 | $json = $this->newTestedInstance('{"foo":"bar"}');
47 |
48 | $this->exception(function () use ($json, $accessor): void {
49 | $json->read('jeanmarc', $accessor);
50 | })
51 | ->isInstanceOf('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Json/Json.php:
--------------------------------------------------------------------------------
1 | content = $this->decode((string) $content);
14 | }
15 |
16 | public function getContent()
17 | {
18 | return $this->content;
19 | }
20 |
21 | public function read($expression, PropertyAccessor $accessor)
22 | {
23 | if (\is_array($this->content)) {
24 | $expression = preg_replace('/^root/', '', $expression);
25 | } else {
26 | $expression = preg_replace('/^root./', '', $expression);
27 | }
28 |
29 | // If root asked, we return the entire content
30 | if ('' === trim($expression)) {
31 | return $this->content;
32 | }
33 |
34 | return $accessor->getValue($this->content, $expression);
35 | }
36 |
37 | public function encode($pretty = true)
38 | {
39 | $flags = \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE;
40 |
41 | if (true === $pretty && \defined('JSON_PRETTY_PRINT')) {
42 | $flags |= \JSON_PRETTY_PRINT;
43 | }
44 |
45 | return json_encode($this->content, $flags);
46 | }
47 |
48 | public function __toString(): string
49 | {
50 | return $this->encode(false);
51 | }
52 |
53 | private function decode($content)
54 | {
55 | $result = json_decode($content);
56 |
57 | if (\JSON_ERROR_NONE !== json_last_error()) {
58 | throw new \Exception("The string '$content' is not valid json");
59 | }
60 |
61 | return $result;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/HttpCall/Request.php:
--------------------------------------------------------------------------------
1 | mink = $mink;
34 | }
35 |
36 | /**
37 | * @param string $name
38 | */
39 | public function __call($name, $arguments)
40 | {
41 | return \call_user_func_array([$this->getClient(), $name], $arguments);
42 | }
43 |
44 | /**
45 | * @return Request\BrowserKit
46 | */
47 | private function getClient()
48 | {
49 | if (null === $this->client) {
50 | if ('symfony2' === $this->mink->getDefaultSessionName()) {
51 | $this->client = new Request\Goutte($this->mink);
52 | } else {
53 | $this->client = new Request\BrowserKit($this->mink);
54 | }
55 | }
56 |
57 | return $this->client;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tests/fixtures/www/xml/book.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | My lists
7 |
8 | My books
9 |
10 |
11 |
12 |
13 |
14 | Title
15 | Author
16 | Language
17 | ISBN
18 |
19 |
20 |
21 |
22 | The Grapes of Wrath
23 | John Steinbeck
24 | en
25 | 0140186409
26 |
27 |
28 | The Pearl
29 | John Steinbeck
30 | en
31 | 014017737X
32 |
33 |
34 | Samarcande
35 | Amine Maalouf
36 | fr
37 | 2253051209
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/features/ru/table.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | Функционал: Таблицы
3 |
4 | Сценарий: Тестирование доступа к /table/index.html
5 | Пусть я на странице "/table/index.html"
6 | Тогда я должен видеть "You are about to test table."
7 |
8 | Сценарий: Тестирование столбцов
9 | Пусть я на странице "/table/index.html"
10 |
11 | Тогда я должен видеть 3 столбца в таблице "table"
12 |
13 | И схема столбцов таблицы "table" должна соответствовать:
14 | | columns |
15 | | Lorem |
16 | | Ipsum |
17 | | Integer |
18 |
19 | Сценарий: Тестирование строк
20 | Пусть я на странице "/table/index.html"
21 |
22 | Тогда я должен видеть 2 строки в таблице "table"
23 | И я должен видеть 2 строки в 1 таблице "table"
24 |
25 | И данные в 1 строке таблицы "table" должны соответствовать:
26 | | col1 | col2 |
27 | | Lorem | Ipsum |
28 |
29 | И данные во 2 строке таблицы "table" должны соответствовать:
30 | | col1 | col2 |
31 | | Dolor | Sit |
32 |
33 | Сценарий: Частичное тестирование строк
34 | Пусть я на странице "/table/index.html"
35 |
36 | Тогда я должен видеть 2 строки в таблице "table"
37 | И я должен видеть 2 строки в 1 таблице "table"
38 |
39 | И данные в 1 строке таблицы "table" должны соответствовать:
40 | | col2 |
41 | | Ipsum |
42 |
43 | И данные во 2 строке таблицы "table" должны соответствовать:
44 | | col1 |
45 | | Dolor |
46 |
47 | Сценарий: Тестирование содержимого ячеек
48 | Пусть я на странице "/table/index.html"
49 | Тогда 1 столбец 1 строки в таблице "table" должен содержать "Lorem"
50 | И 2 столбец 1 строки в таблице "table" должен содержать "Ipsum"
51 |
--------------------------------------------------------------------------------
/tests/features/en/table.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | Feature: Browser Feature
3 |
4 | Scenario: Testing access to /table/index.html
5 | Given I am on "/table/index.html"
6 | Then I should see "You are about to test table."
7 |
8 | Scenario: Testing columns
9 | Given I am on "/table/index.html"
10 |
11 | Then I should see 3 columns in the "table" table
12 |
13 | And the columns schema of the "table" table should match:
14 | | columns |
15 | | Lorem |
16 | | Ipsum |
17 | | Integer |
18 |
19 | Scenario: Testing rows
20 | Given I am on "/table/index.html"
21 |
22 | Then I should see 2 rows in the "table" table
23 | And I should see 2 rows in the 1st "table" table
24 |
25 | And the data in the 1st row of the "table" table should match:
26 | | col1 | col2 |
27 | | Lorem | Ipsum |
28 |
29 | And the data in the 2nd row of the "table" table should match:
30 | | col1 | col2 |
31 | | Dolor | Sit |
32 |
33 | Scenario: Partial Testing rows
34 | Given I am on "/table/index.html"
35 |
36 | Then I should see 2 rows in the "table" table
37 | And I should see 2 rows in the 1st "table" table
38 |
39 | And the data in the 1st row of the "table" table should match:
40 | | col2 |
41 | | Ipsum |
42 |
43 | And the data in the 2nd row of the "table" table should match:
44 | | col1 |
45 | | Dolor |
46 |
47 | Scenario: Testing cell content
48 | Given I am on "/table/index.html"
49 | Then the 1st column of the 1st row in the "table" table should contain "Lorem"
50 | And the 2nd column of the 1st row in the "table" table should contain "Ipsum"
51 | And the 3rd column of the 1st row in the "table" table should contain "42"
52 |
--------------------------------------------------------------------------------
/tests/units/Json/JsonSchema.php:
--------------------------------------------------------------------------------
1 | newTestedInstance('{}');
10 | $resolver = new \JsonSchema\SchemaStorage(new \JsonSchema\Uri\UriRetriever(), new \JsonSchema\Uri\UriResolver());
11 | $schema->resolve($resolver);
12 | }
13 |
14 | public function test_resolve_with_uri(): void
15 | {
16 | $file = 'file://'.__DIR__.'/../../fixtures/files/schema.json';
17 | $schema = (object) ['id' => $file];
18 | $resolver = new \JsonSchema\SchemaStorage(new \JsonSchema\Uri\UriRetriever(), new \JsonSchema\Uri\UriResolver());
19 | $result = $resolver->resolveRef($file);
20 |
21 | $this->object($result)
22 | ->isEqualTo($schema);
23 | }
24 |
25 | public function test_validate(): void
26 | {
27 | $schema = $this->newTestedInstance('{}');
28 | $json = new \Behatch\Json\Json('{}');
29 | $validator = new \JsonSchema\Validator();
30 | $result = $schema->validate($json, $validator);
31 |
32 | $this->boolean($result)
33 | ->isTrue();
34 | }
35 |
36 | public function test_validate_invalid(): void
37 | {
38 | $schema = $this->newTestedInstance('{ "type": "object", "properties": {}, "additionalProperties": false }');
39 | $json = new \Behatch\Json\Json('{ "foo": { "bar": "foobar" } }');
40 | $validator = new \JsonSchema\Validator();
41 | $this->exception(function () use ($schema, $json, $validator): void {
42 | $schema->validate($json, $validator);
43 | })
44 | ->hasMessage(<<newTestedInstance('php');
11 | $result = $inspector->evaluate($json, 'foo.bar');
12 |
13 | $this->string($result)
14 | ->isEqualTo('foobar');
15 | }
16 |
17 | public function test_evaluate_invalid(): void
18 | {
19 | $json = new \Behatch\Json\Json('{}');
20 | $inspector = $this->newTestedInstance('php');
21 |
22 | $this->exception(function () use ($json, $inspector): void {
23 | $inspector->evaluate($json, 'foo.bar');
24 | })
25 | ->hasMessage("Failed to evaluate expression 'foo.bar'");
26 | }
27 |
28 | public function test_evaluate_javascript_mode(): void
29 | {
30 | $json = new \Behatch\Json\Json('{ "foo": { "bar": "foobar" } }');
31 | $inspector = $this->newTestedInstance('javascript');
32 | $result = $inspector->evaluate($json, 'foo->bar');
33 |
34 | $this->string($result)
35 | ->isEqualTo('foobar');
36 | }
37 |
38 | public function test_evaluate_php_mode(): void
39 | {
40 | $json = new \Behatch\Json\Json('{ "foo": { "bar": "foobar" } }');
41 | $inspector = $this->newTestedInstance('php');
42 | $result = $inspector->evaluate($json, 'foo.bar');
43 |
44 | $this->string($result)
45 | ->isEqualTo('foobar');
46 | }
47 |
48 | public function test_validate(): void
49 | {
50 | $json = new \Behatch\Json\Json('{ "foo": { "bar": "foobar" } }');
51 | $inspector = $this->newTestedInstance('php');
52 | $schema = new \Behatch\Json\JsonSchema('{}');
53 |
54 | $result = $inspector->validate($json, $schema);
55 |
56 | $this->boolean($result)
57 | ->isEqualTo(true);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/behat.yml.dist:
--------------------------------------------------------------------------------
1 | default:
2 | suites:
3 | default:
4 | paths: [ '%paths.base%/tests/features' ]
5 | contexts:
6 | - Behat\MinkExtension\Context\MinkContext
7 | - behatch:context:browser:
8 | timeout: 1
9 | - behatch:context:debug:
10 | screenshotDir: '.'
11 | - behatch:context:json:
12 | evaluationMode: javascript
13 | - behatch:context:rest
14 | - behatch:context:system:
15 | root: '.'
16 | - behatch:context:table
17 | - behatch:context:xml
18 | filters:
19 | tags: '~@user'
20 | extensions:
21 | Behat\MinkExtension:
22 | base_url: 'http://localhost:8080'
23 | files_path: '%paths.base%/tests/fixtures/files'
24 | sessions:
25 | default:
26 | goutte: ~
27 | symfony2:
28 | selenium2:
29 | browser: 'chrome'
30 | capabilities:
31 | browserName: 'chrome'
32 | chrome:
33 | switches: ['--headless', '--disable-gpu', '--no-sandbox' ]
34 | extra_capabilities:
35 | "goog:chromeOptions":
36 | w3c: false
37 | Behatch\Extension: ~
38 |
39 | symfony2:
40 | suites:
41 | default:
42 | filters:
43 | # Ignore @statusCode and @rest tags because Selenium2Driver does not support headers or status code (https://github.com/php-webdriver/php-webdriver/issues/811)
44 | # Ignore @json and @xml tags because response is wrapped inside html tags
45 | tags: '~@user&&~@statusCode&&~@rest&&~@json&&~@xml'
46 | extensions:
47 | Behat\MinkExtension:
48 | default_session: 'symfony2'
49 |
--------------------------------------------------------------------------------
/src/HttpCall/HttpCallListener.php:
--------------------------------------------------------------------------------
1 | contextSupportedVoter = $contextSupportedVoter;
22 | $this->httpCallResultPool = $httpCallResultPool;
23 | $this->mink = $mink;
24 | }
25 |
26 | public static function getSubscribedEvents()
27 | {
28 | return [
29 | StepTested::AFTER => 'afterStep',
30 | ];
31 | }
32 |
33 | public function afterStep(AfterStepTested $event)
34 | {
35 | $testResult = $event->getTestResult();
36 |
37 | if (!$testResult instanceof ExecutedStepResult) {
38 | return;
39 | }
40 |
41 | $httpCallResult = new HttpCallResult(
42 | $testResult->getCallResult()->getReturn()
43 | );
44 |
45 | if ($this->contextSupportedVoter->vote($httpCallResult)) {
46 | $this->httpCallResultPool->store($httpCallResult);
47 |
48 | return true;
49 | }
50 |
51 | // For now to avoid modification on MinkContext
52 | // We add fallback on Mink
53 | try {
54 | $this->httpCallResultPool->store(
55 | new HttpCallResult($this->mink->getSession()->getPage()->getContent())
56 | );
57 | } catch (\LogicException $e) {
58 | // Mink has no response
59 | } catch (\Behat\Mink\Exception\DriverException $e) {
60 | // No Mink
61 | } catch (\WebDriver\Exception\NoSuchDriver $e) {
62 | // A session is either terminated or not started
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Extension.php:
--------------------------------------------------------------------------------
1 | load('http_call.yml');
33 |
34 | $this->loadClassResolver($container);
35 | $this->loadHttpCallListener($container);
36 | }
37 |
38 | public function configure(ArrayNodeDefinition $builder): void
39 | {
40 | }
41 |
42 | private function loadClassResolver(ContainerBuilder $container): void
43 | {
44 | $definition = new Definition('Behatch\Context\ContextClass\ClassResolver');
45 | $definition->addTag(ContextExtension::CLASS_RESOLVER_TAG);
46 | $container->setDefinition('behatch.class_resolver', $definition);
47 | }
48 |
49 | private function loadHttpCallListener(ContainerBuilder $container): void
50 | {
51 | $processor = new \Behat\Testwork\ServiceContainer\ServiceProcessor();
52 | $references = $processor->findAndSortTaggedServices($container, 'behatch.context_voter');
53 | $definition = $container->getDefinition('behatch.context_supported.voter');
54 |
55 | foreach ($references as $reference) {
56 | $definition->addMethodCall('register', [$reference]);
57 | }
58 | }
59 |
60 | public function getCompilerPasses()
61 | {
62 | return [];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tests/features/fr/xml.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | @xml
3 | Fonctionnalité:
4 |
5 | Contexte:
6 | Étant donné je suis sur "/xml/feed.xml"
7 |
8 | Scénario:
9 | Alors le flux XML devrait être valide avec sa DTD
10 |
11 | Scénario:
12 | Alors le flux XML devrait être valide avec le XSD "tests/fixtures/www/xml/schema.xsd"
13 |
14 | Scénario:
15 | Alors le flux XML devrait être valide avec cette XSD :
16 | """
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | """
30 |
31 | Scénario:
32 | Alors le flux XML devrait être valide avec le schéma relax NG "tests/fixtures/www/xml/schema.ng"
33 |
34 | Scénario:
35 | Alors le flux XML devrait être valide avec ce schéma relax NG :
36 | """
37 |
38 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | """
55 |
56 | Scénario:
57 | Étant donné je suis sur "/xml/feed.atom"
58 | Alors le flux atom devrait être valide
59 |
60 | Scénario:
61 | Étant donné je suis sur "/xml/feed.rss"
62 | Alors le flux RSS2 devrait être valide
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Behatch contexts
2 | ================
3 |
4 | [](https://github.com/soyuka/contexts/actions/workflows/ci.yml)
5 |
6 | Behatch contexts provide most common Behat tests.
7 |
8 | Installation
9 | ------------
10 |
11 | This extension requires:
12 |
13 | * Behat 3+
14 | * Mink
15 | * Mink extension
16 |
17 | ### Project dependency
18 |
19 | 1. [Install Composer](https://getcomposer.org/download/)
20 | 2. Require the package with Composer:
21 |
22 | ```
23 | $ composer require --dev soyuka/contexts
24 | ```
25 |
26 | 3. Activate extension by specifying its class in your `behat.yml`:
27 |
28 | ```yaml
29 | # behat.yml
30 | default:
31 | # ...
32 | extensions:
33 | Behatch\Extension: ~
34 | ```
35 |
36 | ### Project bootstraping
37 |
38 | 1. Download the Behatch skeleton with composer:
39 |
40 | ```
41 | $ php composer.phar create-project behatch/skeleton
42 | ```
43 |
44 | Browser, json, table and rest step need a mink configuration, see [Mink
45 | extension](https://github.com/FriendsOfBehat/MinkExtension) for more information.
46 |
47 | Usage
48 | -----
49 |
50 | In `behat.yml`, enable desired contexts:
51 |
52 | ```yaml
53 | default:
54 | suites:
55 | default:
56 | contexts:
57 | - behatch:context:browser
58 | - behatch:context:debug
59 | - behatch:context:system
60 | - behatch:context:json
61 | - behatch:context:table
62 | - behatch:context:rest
63 | - behatch:context:xml
64 | ```
65 |
66 | ### Examples
67 |
68 | This project is self-tested, you can explore the [features
69 | directory](./tests/features) to find some examples.
70 |
71 | Configuration
72 | -------------
73 |
74 | * `browser` - more browser related steps (like mink)
75 | * `timeout` - default timeout
76 | * `debug` - helper steps for debugging
77 | * `screenshotDir` - the directory where store screenshots
78 | * `system` - shell related steps
79 | * `root` - the root directory of the filesystem
80 | * `json` - JSON related steps
81 | * `evaluationMode` - javascript "foo.bar" or php "foo->bar"
82 | * `table` - play with HTML the tables
83 | * `rest` - send GET, POST, ... requests and test the HTTP headers
84 | * `xml` - XML related steps
85 |
86 | ### Configuration Example
87 |
88 | For example, if you want to change default directory to screenshots - you can do it this way:
89 |
90 | ```yaml
91 | default:
92 | suites:
93 | default:
94 | contexts:
95 | - behatch:context:debug:
96 | screenshotDir: "var"
97 | ```
98 |
--------------------------------------------------------------------------------
/tests/features/ja/browser.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @browser
3 | フィーチャ: Browser Feature
4 |
5 | もしテストに失敗した場合は
6 | セットアップがうまくいっていない可能性があります。
7 | README.mdをご一読ください
8 |
9 | @javascript
10 | シナリオ: Testing simple web access
11 | 前提 "/index.html" を表示している
12 | ならば 画面に "Congratulations, you've correctly set up your apache environment." と表示されていること
13 |
14 | @statusCode
15 | シナリオ: Basic authentication
16 | 前提 "/browser/auth.php" を表示している
17 | ならば レスポンスコードが 401 であること
18 | かつ 画面に "NONE SHALL PASS" と表示されていること
19 |
20 | もし Basic認証を"something"と"wrong"で設定する
21 | かつ "/browser/auth.php" へ移動する
22 | ならば レスポンスコードが 401 であること
23 | かつ 画面に "NONE SHALL PASS" と表示されていること
24 |
25 | もし Basic認証を"gabriel"と"30091984"で設定する
26 | かつ "/browser/auth.php" へ移動する
27 | ならば レスポンスコードが 200 であること
28 | かつ 画面に "Successfuly logged in" と表示されていること
29 |
30 | もし "/browser/auth.php?logout" へ移動する
31 | かつ 画面に "Logged out" と表示されていること
32 |
33 | かつ "/browser/auth.php" へ移動する
34 | ならば レスポンスコードが 401 であること
35 | かつ 画面に "NONE SHALL PASS" と表示されていること
36 |
37 | @javascript
38 | シナリオ: Elements testing
39 | 前提 下記から構成されるURLに遷移する:
40 | | parameters |
41 | | /browser |
42 | | /elements.html |
43 | ならば 1番目の"body"要素が4個の"div"要素を持つこと
44 | ならば 1番目の"body"要素が6個以下の"div"要素を持つこと
45 | ならば 1番目の"body"要素が2個以上の"div"要素を持つこと
46 | かつ セレクトボックス"months_selector"は"january"を含むこと
47 | かつ セレクトボックス"months_selector"は"december"を含まないこと
48 | もし 私が 1 番目の "ul li" 要素をクリックする
49 | ならば 画面に "You clicked First" と表示されていること
50 |
51 | @javascript
52 | シナリオ: Frames testing
53 | 前提 "/browser/frames.html" を表示している
54 | もし 私が "index" iframeにフォーカスする
55 | ならば 画面に "Visible" と表示されていること
56 |
57 | もし 私が メインフレームにフォーカスする
58 |
59 | もし 私が "elements" iframeにフォーカスする
60 | ならば セレクトボックス"months_selector"は"january"を含むこと
61 |
62 | @javascript
63 | シナリオ: Wait before seeing
64 | 前提 "/browser/timeout.html" を表示している
65 | ならば 私が"timeout"を見るまで3秒間待つ
66 | かつ 私が1秒間待つ
67 | かつ 私が"#iframe"要素を見るまで待つ
68 | かつ 私が "#iframe" 要素を見るまで 5 秒間待つ
69 | かつ 私が "#iframe" 要素を見るまで 5 秒待つ
70 | かつ "#iframe" 要素を見るまで 5 秒待つ
71 |
72 | @javascript
73 | シナリオ: Check element visibility
74 | 前提 "/browser/index.html" を表示している
75 | ならば 要素"#visible-element"は可視であること
76 | かつ 要素"#hidden-element"は不可視であること
77 |
78 | @javascript
79 | シナリオ:
80 | 前提 "/browser/elements.html" を表示している
81 | ならば 私が"today"に現在の日付を入力する
82 | かつ 私が"today"に現在の日付を"-1 day"で入力する
83 |
--------------------------------------------------------------------------------
/tests/features/pt/rest.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | @rest
3 | Funcionalidade: Testando o RESTContext
4 |
5 | Cenário: Testando headers
6 | Quando envio uma requisição GET para "rest/index.php"
7 | Então o header "Content-Type" deve conter "text"
8 | E o header "Content-Type" deve ser igual a "text/html; charset=UTF-8"
9 | E o header "Content-Type" não deve conter "text/json"
10 | E o header "xxx" não deve existir
11 | E a resposta deve expirar no futuro
12 | E a resposta deve estar codificada em "UTF-8"
13 |
14 | Cenário: Testando métodos de requisição
15 | Quando envio uma requisição GET para "/rest/index.php"
16 | Então devo ver "You have sent a GET request. "
17 | E devo ver "No parameter received"
18 |
19 | Quando envio uma requisição GET para "/rest/index.php?first=foo&second=bar"
20 | Então devo ver "You have sent a GET request. "
21 | E devo ver "2 parameter(s)"
22 | E devo ver "first : foo"
23 | E devo ver "second : bar"
24 |
25 | Quando envio uma requisição POST para "/rest/index.php" com os parâmetros:
26 | | key | value |
27 | | foo | bar |
28 | | foofile | @lorem.txt |
29 | Então devo ver "You have sent a POST request. "
30 | E devo ver "1 parameter(s)"
31 | E devo ver "1 file(s)"
32 | E devo ver "foo : bar"
33 | E devo ver "foofile - name : lorem.txt"
34 | E devo ver "foofile - error : 0"
35 | E devo ver "foofile - size : 39"
36 |
37 | Quando envio uma requisição PUT para "/rest/index.php"
38 | Então devo ver "You have sent a PUT request. "
39 |
40 | Quando envio uma requisição DELETE para "/rest/index.php"
41 | Então devo ver "You have sent a DELETE request. "
42 |
43 | Quando envio uma requisição POST para "/rest/index.php" com o corpo:
44 | """
45 | This is a body.
46 | """
47 | Então devo ver "Body : This is a body."
48 |
49 | Quando envio uma requisição PUT para "/rest/index.php" com o corpo:
50 | """
51 | {"this is":"some json"}
52 | """
53 | Então a resposta deve estar vazia
54 |
55 | Cenário: Adicionar um header
56 | Quando adiciono o header "xxx" com o valor "yyy"
57 | E envio uma requisição GET para "/rest/index.php"
58 | Então devo ver "HTTP_XXX : yyy"
59 |
60 | Cenário: Nome do header case-insensitive
61 | Como descrito na rfc2614 §4.2
62 | https://tools.ietf.org/html/rfc2616#section-4.2
63 |
64 | Quando envio uma requisição GET para "rest/index.php"
65 | Então o header "content-type" deve conter "text"
66 |
67 | Cenário: Debug
68 | Quando adiciono o header "xxx" com o valor "yyy"
69 | E envio uma requisição POST para "/rest/index.php" com os parâmetros:
70 | | key | value |
71 | | foo | bar |
72 | Então exiba os headers da última resposta
73 | E exiba o comando curl correspondente
74 |
--------------------------------------------------------------------------------
/tests/features/fr/rest.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | @rest
3 | Fonctionnalité:
4 |
5 | Scénario:
6 | Quand j'envoie une requête GET sur "rest/index.php"
7 | Et l'entête "Content-Type" devrait contenir "text"
8 | Et l'entête "Content-Type" devrait être égal à "text/html; charset=UTF-8"
9 | Et l'entête "Content-Type" ne devrait pas contenir "text/json"
10 | Et l'entête "xxx" ne devrait pas exister
11 | Et la réponse devrait expirer dans le futur
12 | Et la réponse devrait être encodée en "UTF-8"
13 |
14 | Scénario:
15 | Étant donné j'envoie une requête GET sur "rest/index.php"
16 | Alors je devrais voir "You have sent a GET request. "
17 | Et je devrais voir "No parameter received"
18 |
19 | Quand j'envoie une requête GET sur "/rest/index.php?first=foo&second=bar"
20 | Alors je devrais voir "You have sent a GET request. "
21 | Et je devrais voir "2 parameter(s)"
22 | Et je devrais voir "first : foo"
23 | Et je devrais voir "second : bar"
24 |
25 | Quand j'envoie une requête POST sur "/rest/index.php" avec les paramètres :
26 | | key | value |
27 | | foo | bar |
28 | | foofile | @lorem.txt |
29 | Alors je devrais voir "You have sent a POST request. "
30 | Et je devrais voir "1 parameter(s)"
31 | Et je devrais voir "1 file(s)"
32 | Et je devrais voir "foo : bar"
33 | Et je devrais voir "foofile - name : lorem.txt"
34 | Et je devrais voir "foofile - error : 0"
35 | Et je devrais voir "foofile - size : 39"
36 |
37 | Quand j'envoie une requête PUT sur "rest/index.php"
38 | Alors je devrais voir "You have sent a PUT request. "
39 |
40 | Quand j'envoie une requête DELETE sur "rest/index.php"
41 | Alors je devrais voir "You have sent a DELETE request. "
42 |
43 | Quand j'envoie une requête POST sur "/rest/index.php" avec le contenu :
44 | """
45 | This is a body.
46 | """
47 | Alors je devrais voir "Body : This is a body."
48 |
49 | Scénario:
50 | Étant donné j'ajoute l'entête "xxx" égale à "yyy"
51 | Quand j'envoie une requête GET sur "/rest/index.php"
52 | Alors je devrais voir "HTTP_XXX : yyy"
53 |
54 | Scénario: Nom d'entête insenible à la casse
55 | Comme décrit dans la rfc2616 §4.2
56 | https://tools.ietf.org/html/rfc2616#section-4.2
57 |
58 | Quand j'envoie une requête GET sur "/rest/index.php"
59 | Alors l'entête "Content-Type" devrait contenir "text"
60 |
61 | Scénario: Debug
62 | Étant donné j'ajoute l'entête "xxx" égale à "yyy"
63 | Quand j'envoie une requête POST sur "/rest/index.php" avec les paramètres :
64 | | key | value |
65 | | foo | bar |
66 | Alors imprimer les entêtes de la dernière réponse
67 | Et imprimer la commande curl correspondante
68 |
--------------------------------------------------------------------------------
/tests/features/ja/rest.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @rest
3 | フィーチャ: Testing RESTContext
4 |
5 | シナリオ: Testing headers
6 | もし 私がGETメソッドで"rest/index.php"へリクエストを送る
7 | かつ "Content-Type"ヘッダが"text"を含むこと
8 | かつ "Content-Type"ヘッダが"text/html; charset=UTF-8"と一致すること
9 | かつ "Content-Type"ヘッダが"text/json"を含まないこと
10 | かつ "xxx"ヘッダが存在しないこと
11 | かつ レスポンスが将来期限切れになること
12 | かつ レスポンスが"UTF-8"でエンコードされていること
13 | # ならば ブレークポイントを設置する
14 |
15 | シナリオ: Testing request methods.
16 | 前提 私がGETメソッドで"/rest/index.php"へリクエストを送る
17 | ならば 画面に "You have sent a GET request. " と表示されていること
18 | かつ 画面に "No parameter received" と表示されていること
19 |
20 | もし 私がGETメソッドで"/rest/index.php?first=foo&second=bar"へリクエストを送る
21 | ならば 画面に "You have sent a GET request. " と表示されていること
22 | かつ 画面に "2 parameter(s)" と表示されていること
23 | かつ 画面に "first : foo" と表示されていること
24 | かつ 画面に "second : bar" と表示されていること
25 |
26 | # ならば ブレークポイントを設置する
27 | もし POSTメソッドで"/rest/index.php"へ下記のパラメーターを伴ったリクエストを送る:
28 | | key | value |
29 | | foo | bar |
30 | | foofile | @lorem.txt |
31 |
32 | ならば 最後のレスポンスを表示
33 | ならば 画面に "You have sent a POST request. " と表示されていること
34 | かつ 画面に "1 parameter(s)" と表示されていること
35 | かつ 画面に "1 file(s)" と表示されていること
36 | かつ 画面に "foo : bar" と表示されていること
37 | かつ 画面に "foofile - name : lorem.txt" と表示されていること
38 | かつ 画面に "foofile - error : 0" と表示されていること
39 | かつ 画面に "foofile - size : 39" と表示されていること
40 |
41 | もし 私がPUTメソッドで"rest/index.php"へリクエストを送る
42 | ならば 画面に "You have sent a PUT request. " と表示されていること
43 |
44 | もし 私がDELETEメソッドで"rest/index.php"へリクエストを送る
45 | ならば 画面に "You have sent a DELETE request. " と表示されていること
46 |
47 | もし POSTメソッドで"/rest/index.php"へ下記のボディを持ったリクエストを送る:
48 | """
49 | This is a body.
50 | """
51 | ならば 3秒間待つ
52 | ならば 最後のレスポンスを表示
53 | ならば 画面に "Body : This is a body." と表示されていること
54 |
55 | もし PUTメソッドで"/rest/index.php"へ下記のボディを持ったリクエストを送る:
56 | """
57 | {"this is":"some json"}
58 | """
59 | ならば レスポンスが空であること
60 | # ならば ブレークポイントを設置する
61 |
62 | シナリオ: Add header
63 | 前提 "xxx"ヘッダに"yyy"を追加する
64 | もし 私がGETメソッドで"/rest/index.php"へリクエストを送る
65 | ならば 画面に "HTTP_XXX : yyy" と表示されていること
66 | # ならば ブレークポイントを設置する
67 |
68 | シナリオ: Case-insensitive header name
69 | Like describe in the rfc2614 §4.2
70 | https://tools.ietf.org/html/rfc2616#section-4.2
71 |
72 | もし 私がGETメソッドで"/rest/index.php"へリクエストを送る
73 | かつ "Content-Type"ヘッダが"text"を含むこと
74 | # ならば ブレークポイントを設置する
75 |
76 | シナリオ: Debug
77 | 前提 "xxx"ヘッダに"yyy"を追加する
78 | もし POSTメソッドで"/rest/index.php"へ下記のパラメーターを伴ったリクエストを送る:
79 | | key | value |
80 | | foo | bar |
81 | ならば 最後のレスポンスヘッダを表示する
82 | かつ curlコマンドを表示する
83 |
--------------------------------------------------------------------------------
/tests/features/pt/browser.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | Funcionalidade: Browser
3 |
4 | # Se este cenário falhar
5 | # Seu ambiente não deve estar configurado corretamente
6 | # Você pode encontrar a ajuda necessária no README.md
7 | @javascript
8 | Cenário: Testando um acesso web simples
9 | Quando estou em "/index.html"
10 | Então devo ver "Congratulations, you've correctly set up your apache environment."
11 |
12 | @statusCode
13 | Cenário: Basic Authentication
14 | Quando Eu estou em "/browser/auth.php"
15 | Então o código de status da resposta deve ser 401
16 | E devo ver "NONE SHALL PASS"
17 |
18 | Quando eu preencho a autenticação com "something" e "wrong"
19 | E vou para "/browser/auth.php"
20 | Então o código de status da resposta deve ser 401
21 | E devo ver "NONE SHALL PASS"
22 |
23 | Quando eu preencho a autenticação com "gabriel" e "30091984"
24 | E vou para "/browser/auth.php"
25 | Então o código de status da resposta deve ser 200
26 | E devo ver "Successfuly logged in"
27 |
28 | Quando Eu vou para "/browser/auth.php?logout"
29 | Então devo ver "Logged out"
30 |
31 | Quando Eu vou para "/browser/auth.php"
32 | Então o código de status da resposta deve ser 401
33 | E devo ver "NONE SHALL PASS"
34 |
35 | @javascript
36 | Cenário: Testando elementos
37 | Quando Eu estou em uma url composta por:
38 | | parameters |
39 | | /browser |
40 | | /elements.html |
41 | Então devo ver 4 "div" no 1º "body"
42 | E devo ver menos que 6 "div" no 1º "body"
43 | E devo ver mais que 2 "div" no 1º "body"
44 | E o select "months_selector" não deve conter "december"
45 | E o select "months_selector" deve conter "january"
46 | Quando Eu clico no 1º elemento "ul li"
47 | Então Eu devo ver "You clicked First"
48 |
49 | @javascript
50 | Cenário: Testando frames
51 | Quando Eu estou em "/browser/frames.html"
52 | E mudo para o iframe "index"
53 | Então devo ver "Visible"
54 |
55 | Quando eu mudo para o frame principal
56 |
57 | Quando mudo para o iframe "elements"
58 | Então o select "months_selector" deve conter "january"
59 |
60 | @javascript
61 | Cenário: Esperar antes de ver
62 | Quando Eu estou em "/browser/timeout.html"
63 | Então espero 3 segundos até ver "timeout"
64 | E espero 1 segundo
65 | E espero pelo elemento "#iframe"
66 | E espero 5 segundos pelo elemento "#iframe"
67 |
68 | @javascript
69 | Cenário: Verificar visibilidade do elemento
70 | Quando Eu estou em "/browser/index.html"
71 | Então o elemento "#visible-element" deve estar visível
72 | E o elemento "#hidden-element" não deve estar visível
73 |
74 | @javascript
75 | Cenário:
76 | Quando Eu estou em "/browser/elements.html"
77 | Então Eu preencho "today" com a data atual
78 | E Eu preencho "today" com a data atual e o modificador "-1 day"
79 |
80 |
81 | Cenário:
82 | Quando Eu estou em "/browser/elements.html"
83 | Então Eu salvo o valor de "today" no parâmetro "today"
84 |
--------------------------------------------------------------------------------
/src/Asserter.php:
--------------------------------------------------------------------------------
1 | getSession()->getDriver());
18 | }
19 |
20 | protected function assert($test, $message): void
21 | {
22 | if (false === $test) {
23 | throw new ExpectationException($message, $this->getSession()->getDriver());
24 | }
25 | }
26 |
27 | protected function assertContains($expected, $actual, $message = null): void
28 | {
29 | $regex = '/'.preg_quote($expected, '/').'/ui';
30 |
31 | $this->assert(
32 | preg_match($regex, $actual) > 0,
33 | $message ?: "The string '$expected' was not found."
34 | );
35 | }
36 |
37 | protected function assertNotContains($expected, $actual, $message = null): void
38 | {
39 | $message = $message ?: "The string '$expected' was found.";
40 |
41 | $this->not(function () use ($expected, $actual): void {
42 | $this->assertContains($expected, $actual);
43 | }, $message);
44 | }
45 |
46 | protected function assertCount($expected, array $elements, $message = null): void
47 | {
48 | $this->assert(
49 | (int) $expected === \count($elements),
50 | $message ?: \sprintf('%d elements found, but should be %d.', \count($elements), $expected)
51 | );
52 | }
53 |
54 | protected function assertEquals($expected, $actual, $message = null): void
55 | {
56 | $this->assert(
57 | $expected == $actual,
58 | $message ?: "The element '$actual' is not equal to '$expected'"
59 | );
60 | }
61 |
62 | protected function assertSame($expected, $actual, $message = null): void
63 | {
64 | $this->assert(
65 | $expected === $actual,
66 | $message ?: "The element '$actual' is not equal to '$expected'"
67 | );
68 | }
69 |
70 | protected function assertArrayHasKey($key, $array, $message = null): void
71 | {
72 | $this->assert(
73 | isset($array[$key]),
74 | $message ?: "The array has no key '$key'"
75 | );
76 | }
77 |
78 | protected function assertArrayNotHasKey($key, $array, $message = null): void
79 | {
80 | $message = $message ?: "The array has key '$key'";
81 |
82 | $this->not(function () use ($key, $array): void {
83 | $this->assertArrayHasKey($key, $array);
84 | }, $message);
85 | }
86 |
87 | protected function assertTrue($value, $message = 'The value is false'): void
88 | {
89 | $this->assert($value, $message);
90 | }
91 |
92 | protected function assertFalse($value, $message = 'The value is true'): void
93 | {
94 | $this->not(function () use ($value): void {
95 | $this->assertTrue($value);
96 | }, $message);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Xml/Dom.php:
--------------------------------------------------------------------------------
1 | dom = new \DOMDocument();
12 | $this->dom->strictErrorChecking = false;
13 | $this->dom->validateOnParse = false;
14 | $this->dom->preserveWhiteSpace = true;
15 | $this->dom->loadXML($content, \LIBXML_PARSEHUGE);
16 | $this->throwError();
17 | }
18 |
19 | public function __toString(): string
20 | {
21 | $this->dom->formatOutput = true;
22 |
23 | return $this->dom->saveXML();
24 | }
25 |
26 | public function validate(): void
27 | {
28 | $this->dom->validate();
29 | $this->throwError();
30 | }
31 |
32 | public function validateXsd($xsd): void
33 | {
34 | $this->dom->schemaValidateSource($xsd);
35 | $this->throwError();
36 | }
37 |
38 | public function validateNg($ng): void
39 | {
40 | try {
41 | $this->dom->relaxNGValidateSource($ng);
42 | $this->throwError();
43 | } catch (\DOMException $e) {
44 | throw new \RuntimeException($e->getMessage());
45 | }
46 | }
47 |
48 | public function xpath($element)
49 | {
50 | $xpath = new \DOMXPath($this->dom);
51 | $this->registerNamespace($xpath);
52 |
53 | $element = $this->fixNamespace($element);
54 | $elements = $xpath->query($element);
55 |
56 | return (false === $elements) ? new \DOMNodeList() : $elements;
57 | }
58 |
59 | private function registerNamespace(\DOMXPath $xpath): void
60 | {
61 | $namespaces = $this->getNamespaces();
62 |
63 | foreach ($namespaces as $prefix => $namespace) {
64 | if (empty($prefix) && $this->hasDefaultNamespace()) {
65 | $prefix = 'rootns';
66 | }
67 | $xpath->registerNamespace($prefix, $namespace);
68 | }
69 | }
70 |
71 | /**
72 | * "fix" queries to the default namespace if any namespaces are defined.
73 | */
74 | private function fixNamespace($element)
75 | {
76 | $namespaces = $this->getNamespaces();
77 |
78 | if (!empty($namespaces) && $this->hasDefaultNamespace()) {
79 | for ($i = 0; $i < 2; ++$i) {
80 | $element = preg_replace('/\/(\w+)(\[[^]]+\])?\//', '/rootns:$1$2/', $element);
81 | }
82 | $element = preg_replace('/\/(\w+)(\[[^]]+\])?$/', '/rootns:$1$2', $element);
83 | }
84 |
85 | return $element;
86 | }
87 |
88 | private function hasDefaultNamespace()
89 | {
90 | $defaultNamespaceUri = $this->dom->lookupNamespaceURI(null);
91 | $defaultNamespacePrefix = $defaultNamespaceUri ? $this->dom->lookupPrefix($defaultNamespaceUri) : null;
92 |
93 | return empty($defaultNamespacePrefix) && !empty($defaultNamespaceUri);
94 | }
95 |
96 | public function getNamespaces()
97 | {
98 | $xml = simplexml_import_dom($this->dom);
99 |
100 | return $xml->getNamespaces(true);
101 | }
102 |
103 | private function throwError(): void
104 | {
105 | $error = libxml_get_last_error();
106 | if (!empty($error)) {
107 | // https://bugs.php.net/bug.php?id=46465
108 | if ('Validation failed: no DTD found !' != $error->message) {
109 | throw new \DOMException($error->message.' at line '.$error->line);
110 | }
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/features/ja/json.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @json
3 | 機能: Testing JSONContext
4 |
5 | シナリオ: Am I a JSON ?
6 | 前提 "/json/imajson.json" を表示している
7 | ならば レスポンスがJSONであること
8 | もし "/json/emptyarray.json" を表示している
9 | ならば レスポンスがJSON形式であること
10 | もし "/json/emptyobject.json" を表示している
11 | ならば レスポンスがJSONであること
12 | もし "/json/imnotajson.json" を表示している
13 | ならば レスポンスがJSONでないこと
14 |
15 | シナリオ: Count JSON elements
16 | 前提 "/json/imajson.json" を表示している
17 | ならば JSONのノード"numbers"が4個の要素を持つこと
18 |
19 | シナリオ: Checking JSON evaluation
20 | 前提 "/json/imajson.json" を表示している
21 |
22 | ならば JSONにノード"foo"が存在すること
23 | かつ JSONにノード"root.foo"が存在すること
24 | かつ JSONのノード"foo"が"bar"を含むこと
25 | かつ JSONのノード"foo"が"something else"を含まないこと
26 |
27 | かつ JSONのノード"numbers[0]"が"öne"を含むこと
28 | かつ JSONのノード"numbers[1]"が"two"を含むこと
29 | かつ JSONのノード"numbers[2]"が"three"を含むこと
30 | かつ JSONのノード"numbers[3].complexeshizzle"が"true"と等しいこと
31 | かつ JSONのノード"numbers[3].so[0]"が"very"と等しいこと
32 | かつ JSONのノード"numbers[3].so[1].complicated"が"indeed"と等しいこと
33 |
34 | かつ JSONにノード"bar"が存在しないこと
35 | # かつ ブレークポイントを設置する
36 |
37 | シナリオ: Json validation with schema
38 | 前提 "/json/imajson.json" を表示している
39 | ならば JSONがスキーマファイル"tests/fixtures/www/json/schema.json"に従っていること
40 | # かつ ブレークポイントを設置する
41 |
42 | シナリオ: Json validation with schema containing ref
43 | 前提 "/json/withref.json" を表示している
44 | ならば JSONがスキーマファイル"tests/fixtures/www/json/schemaref.json"に従っていること
45 | # かつ ブレークポイントを設置する
46 |
47 | シナリオ: Json validation
48 | 前提 "/json/imajson.json" を表示している
49 | ならば JSONが下記のスキーマに従っていること:
50 | """
51 | {
52 | "type": "object",
53 | "$schema": "http://json-schema.org/draft-03/schema",
54 | "required":true,
55 | "properties": {
56 | "foo": {
57 | "type": "string",
58 | "required":true
59 | },
60 | "numbers": {
61 | "type": "array",
62 | "required":true,
63 | "öne": {
64 | "type": "string",
65 | "required":true
66 | },
67 | "two": {
68 | "type": "string",
69 | "required":true
70 | },
71 | "three": {
72 | "type": "string",
73 | "required":true
74 | }
75 | }
76 | }
77 | }
78 | """
79 | # かつ ブレークポイントを設置する
80 |
81 | シナリオ: Json contents validation
82 | 前提 "/json/imajson.json" を表示している
83 | ならば JSONが下記と一致すること:
84 | """
85 | {
86 | "foo": "bar",
87 | "numbers": [
88 | "öne",
89 | "two",
90 | "three",
91 | {
92 | "complexeshizzle": true,
93 | "so": [
94 | "very",
95 | {
96 | "complicated": "indeed"
97 | }
98 | ]
99 | }
100 | ]
101 | }
102 | """
103 | かつ 最後のJSONレスポンスを表示する
104 | # かつ ブレークポイントを設置する
105 |
106 | シナリオ: Check json root node
107 | 前提 "/json/rootarray.json" を表示している
108 | ならば レスポンスがJSON形式であること
109 | かつ JSONにノード"root[0].name"が存在すること
110 | かつ JSONのノード"root"が2個の要素を持つこと
111 |
--------------------------------------------------------------------------------
/tests/features/fr/browser.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | Fonctionnalité:
3 |
4 | Scénario:
5 | Étant donné je suis sur "/index.html"
6 | Alors je devrais voir "Congratulations, you've correctly set up your apache environment."
7 |
8 | @statusCode
9 | Scénario:
10 | Étant donnée je suis sur "/browser/auth.php"
11 | Alors le code de status de la réponse devrait être 401
12 | Et je devrais voir "NONE SHALL PASS"
13 |
14 | Quand je remplis l'authentification basique avec "something" et "wrong"
15 | Et je vais sur "/browser/auth.php"
16 | Alors le code de status de la réponse devrait être 401
17 | Et je devrais voir "NONE SHALL PASS"
18 |
19 | Quand je remplis l'authentification basique avec "gabriel" et "30091984"
20 | Et je vais sur "/browser/auth.php"
21 | Alors le code de status de la réponse devrait être 200
22 | Et je devrais voir "Successfuly logged in"
23 |
24 | Quand je vais sur "/browser/auth.php?logout"
25 | Et je devrais voir "Logged out"
26 |
27 | Quand je vais sur "/browser/auth.php"
28 | Alors le code de status de la réponse devrait être 401
29 | Et je devrais voir "NONE SHALL PASS"
30 |
31 | @javascript
32 | Scénario:
33 | Étant donné je suis sur une url composée par:
34 | | parameters |
35 | | /browser |
36 | | /elements.html |
37 | Alors je devrais voir 4 "div" dans le 1er "body"
38 | Et je devrais voir moins de 6 "div" dans le 1er "body"
39 | Et je devrais voir plus de 2 "div" dans le 1er "body"
40 | Et la liste de sélection "months_selector" ne devrait pas contenir "december"
41 | Et la liste de sélection "months_selector" devrait contenir "january"
42 | Quand je clique sur le 1er élément "ul li"
43 | Alors je devrais voir "You clicked First"
44 | Quand je presse le 2nd "Submit" bouton
45 | Alors je devrais voir "You clicked Second BUTTON"
46 | Quand je suis le 1ier "Second" lien
47 | Alors je devrais voir "You clicked Second A"
48 |
49 | @javascript
50 | Scénario:
51 | Étant donné je suis sur "/browser/frames.html"
52 | Quand je bascule vers l'iframe "index"
53 | Alors je devrais voir "Visible"
54 |
55 | Quand je bascule vers le cadre principal
56 |
57 | Quand je bascule vers le cadre "elements"
58 | Alors la liste de sélection "months_selector" devrait contenir "january"
59 |
60 | @javascript
61 | Scénario:
62 | Étant donnée je vais sur "/browser/timeout.html"
63 | Quand j'attends 3 secondes de voir "timeout"
64 | Et j'attends 1 seconde
65 | Et j'attends l'élément "#iframe"
66 | Et j'attends 5 secondes l'élément "#iframe"
67 | Alors le temps total écoulé devrait être "more" que 1 secondes
68 |
69 | @javascript
70 | Scénario:
71 | Étant donné je suis sur "/browser/timeout.html"
72 | Alors je ne devrais pas voir "timeout"
73 | Quand j'attends 3 secondes de voir "timeout"
74 | Alors je devrais voir "timeout"
75 |
76 | Scénario:
77 | Étant donné je suis sur "/browser/index.html"
78 | Alors je ne devrais pas voir "foobar" durant 1 seconde
79 |
80 | @javascript
81 | Scénario:
82 | Étant donné je suis sur "/browser/index.html"
83 | Alors l'élément "#visible-element" devrait être visible
84 | Et l'élément "#hidden-element" ne devrait pas être visible
85 |
86 | @javascript
87 | Scénario:
88 | Étant donné je suis sur "/browser/elements.html"
89 | Alors je remplis "today" avec la date actuelle
90 | Et je remplis "today" avec la date actuelle et modificateur "-1 day"
91 |
92 | Scénario:
93 | Étant donné je suis sur "/browser/elements.html"
94 | Alors je sauvegarde la valeur de "today" dans le paramètre "today"
95 |
96 | Scénario:
97 | Étant donnée je suis sur "/browser/index.html"
98 | Et j'attends 1.9 seconde
99 | Et j'attends 1.9 seconde
100 | Et j'attends 1.9 seconde
101 | Alors le temps total écoulé devrait être "more" que 4 secondes
102 |
--------------------------------------------------------------------------------
/.php-cs-fixer.dist.php:
--------------------------------------------------------------------------------
1 | in(__DIR__)
7 | ->exclude([
8 | 'src/Core/Bridge/Symfony/Maker/Resources/skeleton',
9 | 'tests/Fixtures/app/var',
10 | 'tests/Fixtures/Symfony/Maker',
11 | ])
12 | ->notPath('src/Symfony/Bundle/DependencyInjection/Configuration.php')
13 | ->notPath('src/Annotation/ApiFilter.php') // temporary
14 | ->notPath('src/Annotation/ApiProperty.php') // temporary
15 | ->notPath('src/Annotation/ApiResource.php') // temporary
16 | ->notPath('src/Annotation/ApiSubresource.php') // temporary
17 | ->notPath('tests/Fixtures/TestBundle/Entity/DummyPhp8.php') // temporary
18 | ->append([
19 | 'tests/Fixtures/app/console',
20 | ]);
21 |
22 | $defaultIgnoreTags = (new PhpCsFixer\Fixer\DoctrineAnnotation\DoctrineAnnotationSpacesFixer())
23 | ->getConfigurationDefinition()
24 | ->resolve([])['ignored_tags'] ?? []
25 | ;
26 |
27 | return (new PhpCsFixer\Config())
28 | ->setRiskyAllowed(true)
29 | ->setRules([
30 | '@DoctrineAnnotation' => true,
31 | '@PHP71Migration' => true,
32 | '@PHP71Migration:risky' => true,
33 | '@PHPUnit60Migration:risky' => true,
34 | '@Symfony' => true,
35 | '@Symfony:risky' => true,
36 | 'align_multiline_comment' => [
37 | 'comment_type' => 'phpdocs_like',
38 | ],
39 | 'array_indentation' => true,
40 | 'compact_nullable_typehint' => true,
41 | 'doctrine_annotation_array_assignment' => [
42 | 'operator' => '=',
43 | ],
44 | 'doctrine_annotation_spaces' => [
45 | 'after_array_assignments_equals' => false,
46 | 'before_array_assignments_equals' => false,
47 | 'ignored_tags' => array_merge($defaultIgnoreTags, [
48 | // Behat step tags
49 | 'Given',
50 | 'When',
51 | 'Then',
52 | ]),
53 | ],
54 | 'explicit_indirect_variable' => true,
55 | 'fully_qualified_strict_types' => true,
56 | 'logical_operators' => true,
57 | 'multiline_comment_opening_closing' => true,
58 | 'multiline_whitespace_before_semicolons' => [
59 | 'strategy' => 'no_multi_line',
60 | ],
61 | 'no_alternative_syntax' => true,
62 | 'no_extra_blank_lines' => [
63 | 'tokens' => [
64 | 'break',
65 | 'continue',
66 | 'curly_brace_block',
67 | 'extra',
68 | 'parenthesis_brace_block',
69 | 'return',
70 | 'square_brace_block',
71 | 'throw',
72 | 'use',
73 | ],
74 | ],
75 | 'no_superfluous_elseif' => true,
76 | 'no_superfluous_phpdoc_tags' => [
77 | 'allow_mixed' => false,
78 | ],
79 | 'no_unset_cast' => true,
80 | 'no_unset_on_property' => true,
81 | 'no_useless_else' => true,
82 | 'no_useless_return' => true,
83 | 'ordered_imports' => [
84 | 'imports_order' => [
85 | 'class',
86 | 'function',
87 | 'const',
88 | ],
89 | 'sort_algorithm' => 'alpha',
90 | ],
91 | 'php_unit_method_casing' => [
92 | 'case' => 'camel_case',
93 | ],
94 | 'php_unit_set_up_tear_down_visibility' => true,
95 | 'php_unit_test_annotation' => [
96 | 'style' => 'prefix',
97 | ],
98 | 'phpdoc_add_missing_param_annotation' => [
99 | 'only_untyped' => true,
100 | ],
101 | 'phpdoc_no_alias_tag' => true,
102 | 'phpdoc_order' => true,
103 | 'phpdoc_trim_consecutive_blank_line_separation' => true,
104 | 'phpdoc_var_annotation_correct_order' => true,
105 | 'return_assignment' => true,
106 | 'strict_param' => true,
107 | 'visibility_required' => [
108 | 'elements' => [
109 | 'const',
110 | 'method',
111 | 'property',
112 | ],
113 | ],
114 | 'declare_strict_types' => false,
115 | ])
116 | ->setFinder($finder);
117 |
--------------------------------------------------------------------------------
/tests/features/en/browser.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | Feature: Browser Feature
3 |
4 | # If this scenario fails
5 | # It's probably because your web environment is not properly setup
6 | # You will find the necessery help in README.md
7 | @javascript
8 | Scenario: Testing simple web access
9 | Given I am on "/index.html"
10 | Then I should see "Congratulations, you've correctly set up your apache environment."
11 |
12 | @statusCode
13 | Scenario: Basic authentication
14 | Given I am on "/browser/auth.php"
15 | Then the response status code should be 401
16 | And I should see "NONE SHALL PASS"
17 |
18 | When I set basic authentication with "something" and "wrong"
19 | And I go to "/browser/auth.php"
20 | Then the response status code should be 401
21 | And I should see "NONE SHALL PASS"
22 |
23 | When I set basic authentication with "gabriel" and "30091984"
24 | And I go to "/browser/auth.php"
25 | Then the response status code should be 200
26 | And I should see "Successfuly logged in"
27 |
28 | When I go to "/browser/auth.php?logout"
29 | Then I should see "Logged out"
30 |
31 | When I go to "/browser/auth.php"
32 | Then the response status code should be 401
33 | And I should see "NONE SHALL PASS"
34 |
35 | @javascript
36 | Scenario: Elements testing
37 | Given I am on url composed by:
38 | | parameters |
39 | | /browser |
40 | | /elements.html |
41 | Then I should see 4 "div" in the 1st "body"
42 | And I should see less than 6 "div" in the 1st "body"
43 | And I should see more than 2 "div" in the 1st "body"
44 | And the "months_selector" select box should not contain "december"
45 | And the "months_selector" select box should contain "january"
46 | When I click on the 1st "ul li" element
47 | Then I should see "You clicked First LI"
48 | When I press the 2nd "Submit" button
49 | Then I should see "You clicked Second BUTTON"
50 | When I follow the 1st "Second" link
51 | Then I should see "You clicked Second A"
52 |
53 | @javascript
54 | Scenario: Frames testing
55 | Given I am on "/browser/frames.html"
56 | When I switch to iframe "index"
57 | Then I should see "Visible"
58 |
59 | When switch to main frame
60 |
61 | When switch to iframe "elements"
62 | Then the "months_selector" select box should contain "january"
63 |
64 | @javascript
65 | Scenario: Wait before seeing
66 | Given I am on "/browser/timeout.html"
67 | When I wait 3 seconds until I see "timeout"
68 | And I wait 1 second
69 | And I wait for "#iframe" element
70 | And I wait 5 seconds for "#iframe" element
71 | Then the total elapsed time should be more than 1 seconds
72 |
73 | @javascript
74 | Scenario: Waited upon text should actually be visible
75 | Given I am on "/browser/timeout.html"
76 | Then I should not see "timeout"
77 | When I wait 3 seconds until I see "timeout"
78 | Then I should see "timeout"
79 |
80 | Scenario: Waited upon text should actually be visible
81 | Given I am on "/browser/index.html"
82 | Then I should not see "foobar" within 1 second
83 |
84 | @javascript
85 | Scenario: Check element visibility
86 | Given I am on "/browser/index.html"
87 | Then the "#visible-element" element should be visible
88 | And the "#hidden-element" element should not be visible
89 |
90 | @javascript
91 | Scenario:
92 | Given I am on "/browser/elements.html"
93 | Then I fill in "today" with the current date
94 | And I fill in "today" with the current date and modifier "-1 day"
95 |
96 | Scenario:
97 | Given I am on "/browser/elements.html"
98 | Then i save the value of "today" in the "today" parameter
99 |
100 | Scenario: Waiting for fractions of a second
101 | Given I am on "/browser/index.html"
102 | And I wait 1.9 seconds
103 | And I wait 1.9 seconds
104 | And I wait 1.9 seconds
105 | Then the total elapsed time should be more than 4 seconds
106 |
--------------------------------------------------------------------------------
/src/Context/DebugContext.php:
--------------------------------------------------------------------------------
1 | screenshotDir = $screenshotDir;
19 | }
20 |
21 | /**
22 | * Pauses the scenario until the user presses a key. Useful when debugging a scenario.
23 | *
24 | * @Then (I )put a breakpoint
25 | */
26 | public function iPutABreakpoint(): void
27 | {
28 | fwrite(\STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
29 | while ('' == fgets(\STDIN, 1024)) {
30 | }
31 | fwrite(\STDOUT, "\033[u");
32 | }
33 |
34 | /**
35 | * Saving a screenshot.
36 | *
37 | * @When (I )save a screenshot in :filename
38 | */
39 | public function iSaveAScreenshotIn($filename): void
40 | {
41 | sleep(1);
42 | $this->saveScreenshot($filename, $this->screenshotDir);
43 | }
44 |
45 | /**
46 | * @AfterStep
47 | */
48 | public function failScreenshots(AfterStepScope $scope): void
49 | {
50 | if ($scope->getTestResult()->isPassed()) {
51 | return;
52 | }
53 |
54 | $this->displayProfilerLink();
55 |
56 | $suiteName = urlencode(str_replace(' ', '_', $scope->getSuite()->getName()));
57 | $featureName = urlencode(str_replace(' ', '_', $scope->getFeature()->getTitle() ?? ''));
58 |
59 | if ($this->getBackground($scope)) {
60 | $scenarioName = 'background';
61 | } else {
62 | $scenario = $this->getScenario($scope);
63 | $scenarioName = urlencode(str_replace(' ', '_', $scenario->getTitle() ?? ''));
64 | }
65 |
66 | $filename = \sprintf('fail_%s_%s_%s_%s.png', time(), $suiteName, $featureName, $scenarioName);
67 | $this->saveScreenshot($filename, $this->screenshotDir);
68 | }
69 |
70 | private function displayProfilerLink(): void
71 | {
72 | try {
73 | $headers = $this->getMink()->getSession()->getResponseHeaders();
74 | echo "The debug profile URL {$headers['X-Debug-Token-Link'][0]}";
75 | } catch (\Exception $e) {
76 | /* Intentionally leave blank */
77 | }
78 | }
79 |
80 | /**
81 | * @return \Behat\Gherkin\Node\ScenarioInterface
82 | */
83 | private function getScenario(AfterStepScope $scope)
84 | {
85 | $scenarios = $scope->getFeature()->getScenarios();
86 | foreach ($scenarios as $scenario) {
87 | $stepLinesInScenario = array_map(
88 | function (StepNode $step) {
89 | return $step->getLine();
90 | },
91 | $scenario->getSteps()
92 | );
93 | if (\in_array($scope->getStep()->getLine(), $stepLinesInScenario, true)) {
94 | return $scenario;
95 | }
96 | }
97 |
98 | throw new \LogicException('Unable to find the scenario');
99 | }
100 |
101 | /**
102 | * @return \Behat\Gherkin\Node\BackgroundNode|bool
103 | */
104 | private function getBackground(AfterStepScope $scope)
105 | {
106 | $background = $scope->getFeature()->getBackground();
107 | if (!$background) {
108 | return false;
109 | }
110 | $stepLinesInBackground = array_map(
111 | function (StepNode $step) {
112 | return $step->getLine();
113 | },
114 | $background->getSteps()
115 | );
116 | if (\in_array($scope->getStep()->getLine(), $stepLinesInBackground, true)) {
117 | return $background;
118 | }
119 |
120 | return false;
121 | }
122 |
123 | public function saveScreenshot($filename = null, $filepath = null): void
124 | {
125 | try {
126 | parent::saveScreenshot($filename, $filepath);
127 | } catch (UnsupportedDriverActionException $e) {
128 | return;
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/features/ru/browser.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | Функционал: Браузер
3 |
4 | # Если этот сценарий проваливается
5 | # Возможно, это из-за того, что Ваше web-окружение неправильно настроено
6 | # Вы найдёте необходимую помощь в README.md
7 | @javascript
8 | Сценарий: Тестирование простого веб-доступа
9 | Пусть я на странице "/index.html"
10 | Тогда я должен видеть "Congratulations, you've correctly set up your apache environment."
11 |
12 | @statusCode
13 | Сценарий: Basic-аутентификация
14 | Пусть я на странице "/browser/auth.php"
15 | Тогда код ответа сервера должен быть 401
16 | И я должен видеть "NONE SHALL PASS"
17 |
18 | Когда я устанавливаю Basic-аутентификацию с "something" и "wrong"
19 | И я перехожу на "/browser/auth.php"
20 | Тогда код ответа сервера должен быть 401
21 | И я должен видеть "NONE SHALL PASS"
22 |
23 | Когда я устанавливаю Basic-аутентификацию с "gabriel" и "30091984"
24 | И я перехожу на "/browser/auth.php"
25 | Тогда код ответа сервера должен быть 200
26 | И я должен видеть "Successfuly logged in"
27 |
28 | Когда я перехожу на "/browser/auth.php?logout"
29 | Тогда я должен видеть "Logged out"
30 |
31 | Когда я перехожу на "/browser/auth.php"
32 | Тогда код ответа сервера должен быть 401
33 | И я должен видеть "NONE SHALL PASS"
34 |
35 | @javascript
36 | Сценарий: Тестирование элементов
37 | Пусть я на адресе из:
38 | | parameters |
39 | | /browser |
40 | | /elements.html |
41 | Тогда я должен видеть 4 "div" в 1ом "body"
42 | И я должен видеть менее 6 "div" в 1 "body"
43 | И я должен видеть более 2 "div" в 1 "body"
44 | И выпадающий список "months_selector" не должен содержать "december"
45 | И выпадающий список "months_selector" должен содержать "january"
46 | Когда я кликаю на 1 элемент "ul li"
47 | Тогда я должен видеть "You clicked First LI"
48 | Когда я нажимаю на 2 кнопку "Submit"
49 | Тогда я должен видеть "You clicked Second BUTTON"
50 | Когда я кликаю по 1 ссылке "Second"
51 | Тогда я должен видеть "You clicked Second A"
52 |
53 | @javascript
54 | Сценарий: Тестирование фреймов
55 | Пусть я на странице "/browser/frames.html"
56 | Когда я переключаюсь на iframe "index"
57 | Тогда я должен видеть "Visible"
58 |
59 | Когда я переключаюсь на главный фрейм
60 |
61 | Когда переключаюсь на iframe "elements"
62 | Тогда выпадающий список "months_selector" должен содержать "january"
63 |
64 | @javascript
65 | Сценарий: Ожидание перед проверкой
66 | Пусть я на странице "/browser/timeout.html"
67 | Когда я жду 3 секунды пока не увижу "timeout"
68 | И я жду 1 секунду
69 | И я жду элемент "#iframe"
70 | И я жду 5 секунд элемент "#iframe"
71 | Тогда общее время должно быть more чем 1 секунды
72 |
73 | @javascript
74 | Сценарий: Ожидание пока текст не станет видимым
75 | Пусть я на странице "/browser/timeout.html"
76 | Тогда не должен видеть "timeout"
77 | Когда я жду 3 секунды пока не увижу "timeout"
78 | Тогда я должен видеть "timeout"
79 |
80 | Сценарий: Ожидание пока текст не станет видимым
81 | Пусть я на странице "/browser/index.html"
82 | Тогда я не должен видеть "foobar" в течение 1 секунды
83 |
84 | @javascript
85 | Сценарий: Проверка видимости элемента
86 | Пусть я на странице "/browser/index.html"
87 | Тогда элемент "#visible-element" должен быть видимым
88 | И элемент "#hidden-element" не должен быть видимым
89 |
90 | @javascript
91 | Сценарий:
92 | Пусть я на странице "/browser/elements.html"
93 | Тогда я заполняю поле "today" текущей датой
94 | И я заполняю поле "today" текущей датой с поправкой "-1 day"
95 |
96 | Сценарий:
97 | Пусть я на странице "/browser/elements.html"
98 | Тогда я сохраняю значение поля "today" в параметре "today"
99 |
100 | Сценарий: Ожидание долей секунды
101 | Пусть я на странице "/browser/index.html"
102 | И я жду 1.9 секунды
103 | И я жду 1.9 секунды
104 | И я жду 1.9 секунды
105 | Тогда общее время должно быть more чем 4 секунды
106 |
--------------------------------------------------------------------------------
/src/Context/TableContext.php:
--------------------------------------------------------------------------------
1 | getSession()->getPage()->findAll('css', $columnsSelector);
18 |
19 | $this->iShouldSeeColumnsInTheTable(\count($text->getHash()), $table);
20 |
21 | foreach ($text->getHash() as $key => $column) {
22 | $this->assertEquals($column['columns'], $columns[$key]->getText());
23 | }
24 | }
25 |
26 | /**
27 | * Checks that the specified table contains the given number of columns.
28 | *
29 | * @Then (I )should see :count column(s) in the :table table
30 | */
31 | public function iShouldSeeColumnsInTheTable(int $count, string $table): void
32 | {
33 | $columnsSelector = "$table thead tr th";
34 | $columns = $this->getSession()->getPage()->findAll('css', $columnsSelector);
35 |
36 | $this->assertEquals($count, \count($columns));
37 | }
38 |
39 | /**
40 | * Checks that the specified table contains the specified number of rows in its body.
41 | *
42 | * @Then (I )should see :count rows in the :index :table table
43 | */
44 | public function iShouldSeeRowsInTheNthTable(int $count, int $index, string $table): void
45 | {
46 | $actual = $this->countElements('tbody tr', $index, $table);
47 | $this->assertEquals($count, $actual);
48 | }
49 |
50 | /**
51 | * Checks that the specified table contains the specified number of rows in its body.
52 | *
53 | * @Then (I )should see :count row(s) in the :table table
54 | */
55 | public function iShouldSeeRowsInTheTable(int $count, string $table): void
56 | {
57 | $this->iShouldSeeRowsInTheNthTable($count, 1, $table);
58 | }
59 |
60 | /**
61 | * Checks that the data of the specified row matches the given schema.
62 | *
63 | * @Then the data in the :index row of the :table table should match:
64 | */
65 | public function theDataOfTheRowShouldMatch(int $index, string $table, TableNode $text): void
66 | {
67 | $rowsSelector = "$table tbody tr";
68 | $rows = $this->getSession()->getPage()->findAll('css', $rowsSelector);
69 |
70 | if (!isset($rows[$index - 1])) {
71 | throw new \Exception("The row $index was not found in the '$table' table");
72 | }
73 |
74 | $cells = (array) $rows[$index - 1]->findAll('css', 'td');
75 | $cells = array_merge((array) $rows[$index - 1]->findAll('css', 'th'), $cells);
76 |
77 | $hash = current($text->getHash());
78 |
79 | foreach (array_keys($hash) as $columnName) {
80 | // Extract index from column. ex "col2" -> 2
81 | preg_match('/^col(?P\d+)$/', $columnName, $matches);
82 | $index = (int) $matches['index'] - 1;
83 |
84 | $this->assertEquals($hash[$columnName], $cells[$index]->getText());
85 | }
86 | }
87 |
88 | /**
89 | * Checks that the specified cell (column/row) of the table's body contains the specified text.
90 | *
91 | * @Then the :colIndex column of the :rowIndex row in the :table table should contain :text
92 | */
93 | public function theStColumnOfTheStRowInTheTableShouldContain(int $colIndex, int $rowIndex, string $table, string $text): void
94 | {
95 | $rowSelector = "$table tbody tr";
96 | $rows = $this->getSession()->getPage()->findAll('css', $rowSelector);
97 |
98 | if (!isset($rows[$rowIndex - 1])) {
99 | throw new \Exception("The row $rowIndex was not found in the '$table' table");
100 | }
101 |
102 | $row = $rows[$rowIndex - 1];
103 | $cols = $row->findAll('css', 'td');
104 |
105 | if (!isset($cols[$colIndex - 1])) {
106 | throw new \Exception("The column $colIndex was not found in the row $rowIndex of the '$table' table");
107 | }
108 |
109 | $actual = $cols[$colIndex - 1]->getText();
110 |
111 | $this->assertContains($text, $actual);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/tests/features/fr/json.feature:
--------------------------------------------------------------------------------
1 | #language: fr
2 | @json
3 | Fonctionnalité:
4 |
5 | Scénario:
6 | Étant donné je suis sur "/json/imajson.json"
7 | Alors la réponse devrait être du JSON
8 | Quand je suis sur "/json/imnotajson.json"
9 | Alors la réponse ne devrait pas être du JSON
10 | Quand je suis sur "/json/emptyobject.json"
11 | Alors la réponse devrait être du JSON
12 | Quand je suis sur "/json/imnotajson.json"
13 | Alors la réponse ne devrait pas être du JSON
14 |
15 | Scénario:
16 | Étant donné je suis sur "/json/imajson.json"
17 | Alors le nœud JSON "numbers" devrait avoir 4 éléments
18 |
19 | Scénario:
20 | Étant donné je suis sur "/json/imajson.json"
21 |
22 | Alors le nœud JSON "foo" devrait exister
23 | Et le nœud JSON "root.foo" devrait exister
24 | Et le nœud JSON "foo" devrait contenir "bar"
25 | Et le nœud JSON "foo" ne devrait pas contenir "something else"
26 |
27 | Et le nœud JSON "numbers[0]" devrait contenir "öne"
28 | Et le nœud JSON "numbers[1]" devrait contenir "two"
29 | Et le nœud JSON "numbers[2]" devrait contenir "three"
30 | Et le nœud JSON "numbers[3].complexeshizzle" devrait être égal à "true"
31 | Et le nœud JSON "numbers[3].so[0]" devrait être égal à "very"
32 | Et le nœud JSON "numbers[3].so[1].complicated" devrait être égal à "indeed"
33 |
34 | Et les nœuds JSON devraient être égaux à:
35 | | foo | bar |
36 | | numbers[0] | öne |
37 | | numbers[1] | two |
38 | | numbers[2] | three |
39 |
40 | Et les nœuds JSON devraient contenir:
41 | | foo | bar |
42 | | numbers[0] | öne |
43 | | numbers[1] | two |
44 | | numbers[2] | three |
45 |
46 | Et les nœuds JSON ne devraient pas contenir:
47 | | foo | something else |
48 |
49 | Et le nœud JSON "bar" ne devrait pas exister
50 |
51 | Scénario:
52 | Étant donné je suis sur "/json/imajson.json"
53 | Alors le JSON devrait être valide avec le schéma "tests/fixtures/www/json/schema.json"
54 |
55 | Scénario:
56 | Étant donnée je suis sur "/json/withref.json"
57 | Alors le JSON devrait être valide avec le schéma "tests/fixtures/www/json/schemaref.json"
58 |
59 | Scénario: Json validation
60 | Étant donné je suis sur "/json/imajson.json"
61 | Alors le JSON devrait être valide avec ce schéma:
62 | """
63 | {
64 | "type": "object",
65 | "$schema": "http://json-schema.org/draft-03/schema",
66 | "required":true,
67 | "properties": {
68 | "foo": {
69 | "type": "string",
70 | "required":true
71 | },
72 | "numbers": {
73 | "type": "array",
74 | "required":true,
75 | "öne": {
76 | "type": "string",
77 | "required":true
78 | },
79 | "two": {
80 | "type": "string",
81 | "required":true
82 | },
83 | "three": {
84 | "type": "string",
85 | "required":true
86 | }
87 | }
88 | }
89 | }
90 | """
91 |
92 | Scénario: Json contents validation
93 | Étant donné je suis sur "/json/imajson.json"
94 | Alors le JSON devrait être égal à :
95 | """
96 | {
97 | "foo": "bar",
98 | "numbers": [
99 | "öne",
100 | "two",
101 | "three",
102 | {
103 | "complexeshizzle": true,
104 | "so": [
105 | "very",
106 | {
107 | "complicated": "indeed"
108 | }
109 | ]
110 | }
111 | ]
112 | }
113 | """
114 | Et imprimer la dernière réponse JSON
115 |
116 | Scénario:
117 | Étant donnée je suis sur "/json/rootarray.json"
118 | Alors la réponse devrait être du JSON
119 | Et le nœud JSON "root[0].name" devrait exister
120 | Et le nœud JSON "root" devrait avoir 2 éléments
121 |
122 | Scénario:
123 | Étant donnée je suis sur "/json/arraywithtypes.json"
124 | Alors la réponse devrait être du JSON
125 | Et le nœud JSON "root[0]" devrait être nul
126 | Et le nœud JSON "root[1]" devrait être vrai
127 | Et le nœud JSON "root[2]" devrait être faux
128 | Et le nœud JSON "root[3]" devrait être la chaine de caractère "dunglas.fr"
129 | Et le nœud JSON "root[4]" devrait être le nombre 1312
130 | Et le nœud JSON "root[4]" devrait être le nombre 1312.0
131 | Et le nœud JSON "root[5]" devrait être le nombre 1936.2
132 |
--------------------------------------------------------------------------------
/tests/features/ru/rest.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | @rest
3 | Функционал: Тестирование RESTContext
4 |
5 | Сценарий: Тестирование заголовков
6 | Когда я отправляю GET запрос на "rest/index.php"
7 | И заголовок "Content-Type" должен содержать "text"
8 | И заголовок "Content-Type" должен быть равен "text/html; charset=UTF-8"
9 | И заголовок "Content-Type" не должен быть равен "x-test/no-such-type"
10 | И заголовок "Content-Type" не должен содержать "text/json"
11 | И заголовок "xxx" не должен существовать
12 | И ответ должен истекать в будущем
13 | И ответ должен быть закодирован "UTF-8"
14 |
15 | Сценарий: Тестирование методов запросов.
16 | Пусть я отправляю GET запрос на "/rest/index.php"
17 | Тогда я должен видеть "You have sent a GET request. "
18 | И я должен видеть "No parameter received"
19 |
20 | Когда я отправляю GET запрос на "/rest/index.php?first=foo&second=bar"
21 | Тогда я должен видеть "You have sent a GET request. "
22 | И я должен видеть "2 parameter(s)"
23 | И я должен видеть "first : foo"
24 | И я должен видеть "second : bar"
25 |
26 | Когда я отправляю POST запрос на "/rest/index.php" с параметрами:
27 | | key | value |
28 | | foo | bar |
29 | | foofile | @lorem.txt |
30 | Тогда я должен видеть "You have sent a POST request. "
31 | И я должен видеть "1 parameter(s)"
32 | И я должен видеть "1 file(s)"
33 | И я должен видеть "foo : bar"
34 | И я должен видеть "foofile - name : lorem.txt"
35 | И я должен видеть "foofile - error : 0"
36 | И я должен видеть "foofile - size : 39"
37 |
38 | Когда я отправляю PUT запрос на "/rest/index.php"
39 | Тогда я должен видеть "You have sent a PUT request. "
40 |
41 | Когда я отправляю DELETE запрос на "/rest/index.php"
42 | Тогда я должен видеть "You have sent a DELETE request. "
43 |
44 | Когда я отправляю POST запрос на "/rest/index.php" с телом:
45 | """
46 | This is a body.
47 | """
48 | Тогда я должен видеть "Body : This is a body."
49 |
50 | Когда я отправляю PUT запрос на "/rest/index.php" с телом:
51 | """
52 | {"this is":"some json"}
53 | """
54 | Тогда ответ должен быть пустым
55 |
56 | Сценарий: Добавление заголовка
57 | Пусть я добавляю заголовок "xxx" со значением "yyy"
58 | Когда я отправляю GET запрос на "/rest/index.php"
59 | Тогда я должен видеть "HTTP_XXX : yyy"
60 |
61 | Сценарий: Добавление заголовка с огромным числовым значением
62 | Пусть я добавляю заголовок "xxx-large-numeric" со значением "92233720368547758070"
63 | Когда я отправляю GET запрос на "/rest/index.php"
64 | Тогда я должен видеть "HTTP_XXX_LARGE_NUMERIC : 92233720368547758070"
65 |
66 | Сценарий: Заголовок не должен сохраняться между сценариями
67 | Когда я отправляю GET запрос на "/rest/index.php"
68 | Тогда я не должен видеть "HTTP_XXX : yyy"
69 | Тогда я не должен видеть "HTTP_XXX_LARGE_NUMERIC"
70 |
71 | Сценарий: Регистронезависимость имён заголовков
72 | Как описано в rfc2614 §4.2
73 | https://tools.ietf.org/html/rfc2616#section-4.2
74 |
75 | Когда я отправляю GET запрос на "rest/index.php"
76 | Тогда заголовок "content-type" должен содержать "text"
77 |
78 | Сценарий: Отладка
79 | Пусть я добавляю заголовок "xxx" со значением "yyy"
80 | Когда я отправляю POST запрос на "/rest/index.php" с параметрами:
81 | | key | value |
82 | | foo | bar |
83 | Тогда выведи заголовки последнего ответа
84 | И выведи соответствующую команду curl
85 |
86 | Сценарий: Тело ответа
87 | Пусть я отправляю GET запрос на "/"
88 | Тогда ответ должен быть
89 | """
90 | Congratulations, you've correctly set up your apache environment.
91 | """
92 |
93 | @>php5.5
94 | Сценарий: Установка content-заголовка в POST запросе
95 | Когда я добавляю заголовок "Content-Type" со значением "xxx"
96 | Когда я отправляю "POST" запрос на "rest/index.php" с телом:
97 | """
98 | {"name": "test"}
99 | """
100 | Тогда тело ответа должно содержать ">CONTENT_TYPE : xxx"
101 | Тогда тело ответа должно содержать ">HTTP_CONTENT_TYPE : xxx"
102 |
103 | Сценарий: Content-заголовок очищается между сценариями
104 | Когда я отправляю "POST" запрос на "rest/index.php" с телом:
105 | """
106 | {"name": "test"}
107 | """
108 | Тогда тело ответа не должно содержать ">CONTENT_TYPE : xxx"
109 | Тогда тело ответа не должно содержать ">HTTP_CONTENT_TYPE : xxx"
110 |
--------------------------------------------------------------------------------
/tests/features/pt/json.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | @json
3 | Funcionalidade: Testando o JSONContext
4 |
5 | Cenário: Eu sou um JSON?
6 | Quando Eu estou em "/json/imajson.json"
7 | Então a resposta deve estar em JSON
8 | Quando Eu estou em "/json/emptyarray.json"
9 | Então a resposta deve estar em JSON
10 | Quando Eu estou em "/json/emptyobject.json"
11 | Então a resposta deve estar em JSON
12 | Quando Eu estou em "/json/imnotajson.json"
13 | Então a resposta não deve estar em JSON
14 |
15 | Cenário: Contar os elementos JSON
16 | Quando Eu estou em "/json/imajson.json"
17 | Então o nó JSON "numbers" deve ter 4 elementos
18 |
19 | Cenário: Verificar a interpretação do JSON
20 | Quando Eu estou em "/json/imajson.json"
21 |
22 | Então o nó JSON "foo" deve existir
23 | E o nó JSON "root.foo" deve existir
24 | E o nó JSON "foo" deve conter "bar"
25 | E o nó JSON "foo" não deve conter "something else"
26 |
27 | E o nó JSON "numbers[0]" deve conter "öne"
28 | E o nó JSON "numbers[1]" deve conter "two"
29 | E o nó JSON "numbers[2]" deve conter "three"
30 | E o nó JSON "numbers[3].complexeshizzle" deve ser igual a "true"
31 | E o nó JSON "numbers[3].so[0]" deve ser igual a "very"
32 | E o nó JSON "numbers[3].so[1].complicated" deve ser igual a "indeed"
33 |
34 | E os nós JSON devem ser iguais a:
35 | | foo | bar |
36 | | numbers[0] | öne |
37 | | numbers[1] | two |
38 | | numbers[2] | three |
39 |
40 | E os nós JSON devem conter:
41 | | foo | bar |
42 | | numbers[0] | öne |
43 | | numbers[1] | two |
44 | | numbers[2] | three |
45 |
46 | E os nós JSON não devem conter:
47 | | foo | something else |
48 |
49 | E o nó JSON "bar" não deve existir
50 |
51 | Cenário: Validação do JSON com schema
52 | Quando Eu estou em "/json/imajson.json"
53 | Então o JSON deve ser válido de acordo com o schema "tests/fixtures/www/json/schema.json"
54 |
55 | Cenário: Validação do JSON com schema contendo ref (caso inválido)
56 | Quando Eu estou em "/json/withref-invalid.json"
57 | Então o JSON deve ser inválido de acordo com o schema "tests/fixtures/www/json/schemaref.json"
58 |
59 | Cenário: Validação do JSON com schema contendo ref
60 | Quando Eu estou em "/json/withref.json"
61 | Então o JSON deve ser válido de acordo com o schema "tests/fixtures/www/json/schemaref.json"
62 |
63 | Cenário: Validação do JSON
64 | Quando Eu estou em "/json/imajson.json"
65 | Então o JSON deve ser válido de acordo com esse schema:
66 | """
67 | {
68 | "type": "object",
69 | "$schema": "http://json-schema.org/draft-03/schema",
70 | "required":true,
71 | "properties": {
72 | "foo": {
73 | "type": "string",
74 | "required":true
75 | },
76 | "numbers": {
77 | "type": "array",
78 | "required":true,
79 | "öne": {
80 | "type": "string",
81 | "required":true
82 | },
83 | "two": {
84 | "type": "string",
85 | "required":true
86 | },
87 | "three": {
88 | "type": "string",
89 | "required":true
90 | }
91 | }
92 | }
93 | }
94 | """
95 |
96 | Cenário: Validação do conteúdo do JSON
97 | Quando Eu estou em "/json/imajson.json"
98 | Então o JSON deve ser igual a:
99 | """
100 | {
101 | "foo": "bar",
102 | "numbers": [
103 | "öne",
104 | "two",
105 | "three",
106 | {
107 | "complexeshizzle": true,
108 | "so": [
109 | "very",
110 | {
111 | "complicated": "indeed"
112 | }
113 | ]
114 | }
115 | ]
116 | }
117 | """
118 | E exiba a última resposta JSON
119 |
120 | Cenário: Verificar o nó raiz do JSON
121 | Quando Eu estou em "/json/rootarray.json"
122 | Então a resposta deve estar em JSON
123 | E o nó JSON "root[0].name" deve existir
124 | E o nó JSON "root" deve ter 2 elementos
125 |
126 | Cenário: Verificação com comparação de tipos
127 | Quando Eu estou em "/json/arraywithtypes.json"
128 | Então a resposta deve estar em JSON
129 | E o nó JSON "root[0]" deve ser null
130 | E o nó JSON "root[1]" deve ser true
131 | E o nó JSON "root[2]" deve ser false
132 | E o nó JSON "root[3]" deve ser igual a string "dunglas.fr"
133 | E o nó JSON "root[4]" deve ser igual ao número 1312
134 | E o nó JSON "root[4]" deve ser igual ao número 1312.0
135 | E o nó JSON "root[5]" deve ser igual ao número 1936.2
136 |
--------------------------------------------------------------------------------
/src/HttpCall/Request/BrowserKit.php:
--------------------------------------------------------------------------------
1 | mink = $mink;
17 | }
18 |
19 | public function getMethod()
20 | {
21 | return $this->getRequest()
22 | ->getMethod();
23 | }
24 |
25 | public function getUri()
26 | {
27 | return $this->getRequest()
28 | ->getUri();
29 | }
30 |
31 | public function getServer()
32 | {
33 | return $this->getRequest()
34 | ->getServer();
35 | }
36 |
37 | public function getParameters()
38 | {
39 | return $this->getRequest()
40 | ->getParameters();
41 | }
42 |
43 | protected function getRequest()
44 | {
45 | $client = $this->mink->getSession()->getDriver()->getClient();
46 | // BC layer for BrowserKit 2.2.x and older
47 | if (method_exists($client, 'getInternalRequest')) {
48 | $request = $client->getInternalRequest();
49 | } else {
50 | $request = $client->getRequest();
51 | }
52 |
53 | return $request;
54 | }
55 |
56 | public function getContent()
57 | {
58 | return $this->mink->getSession()->getPage()->getContent();
59 | }
60 |
61 | public function send($method, $url, $parameters = [], $files = [], $content = null, $headers = [])
62 | {
63 | foreach ($files as $originalName => &$file) {
64 | if (\is_string($file)) {
65 | $file = new UploadedFile($file, (string) $originalName);
66 | }
67 | }
68 |
69 | $client = $this->mink->getSession()->getDriver()->getClient();
70 |
71 | $client->followRedirects(false);
72 |
73 | // Workaround for https://github.com/symfony/symfony/issues/33393: prevent a default Accept header to be set
74 | if (!isset($headers['HTTP_ACCEPT']) && '' === $client->getServerParameter('HTTP_ACCEPT')) {
75 | $headers['HTTP_ACCEPT'] = null;
76 | }
77 |
78 | $client->request($method, $url, $parameters, $files, $headers, $content);
79 | $client->followRedirects(true);
80 | $this->resetHttpHeaders();
81 |
82 | return $this->mink->getSession()->getPage();
83 | }
84 |
85 | public function setHttpHeader($name, $value): void
86 | {
87 | $client = $this->mink->getSession()->getDriver()->getClient();
88 | if (method_exists($client, 'setHeader')) {
89 | /*
90 | * @var \Goutte\Client $client
91 | */
92 | $client->setHeader($name, $value);
93 | } else {
94 | /**
95 | * @var \Symfony\Component\BrowserKit\HttpBrowser $client
96 | */
97 |
98 | /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
99 | $contentHeaders = ['CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true];
100 | $name = str_replace('-', '_', strtoupper($name));
101 |
102 | // CONTENT_* are not prefixed with HTTP_ in PHP when building $_SERVER
103 | if (!isset($contentHeaders[$name])) {
104 | $name = 'HTTP_'.$name;
105 | }
106 | /* taken from Behat\Mink\Driver\BrowserKitDriver::setRequestHeader */
107 |
108 | $client->setServerParameter($name, $value);
109 | }
110 | }
111 |
112 | public function getHttpHeaders()
113 | {
114 | return array_change_key_case(
115 | $this->mink->getSession()->getResponseHeaders(),
116 | \CASE_LOWER
117 | );
118 | }
119 |
120 | public function getHttpHeader($name)
121 | {
122 | $values = $this->getHttpRawHeader($name);
123 |
124 | return implode(', ', $values);
125 | }
126 |
127 | public function getHttpRawHeader($name)
128 | {
129 | $name = strtolower($name);
130 | $headers = $this->getHttpHeaders();
131 |
132 | if (isset($headers[$name])) {
133 | $value = $headers[$name];
134 | if (!\is_array($headers[$name])) {
135 | $value = [$headers[$name]];
136 | }
137 | } else {
138 | throw new \OutOfBoundsException("The header '$name' doesn't exist");
139 | }
140 |
141 | return $value;
142 | }
143 |
144 | protected function resetHttpHeaders(): void
145 | {
146 | /** @var GoutteClient|BrowserKitClient $client */
147 | $client = $this->mink->getSession()->getDriver()->getClient();
148 |
149 | $client->setServerParameters([]);
150 | if ($client instanceof GoutteClient) {
151 | $client->restart();
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/tests/features/en/rest.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | @rest
3 | Feature: Testing RESTContext
4 |
5 | Scenario: Testing headers
6 | When I send a GET request to "rest/index.php"
7 | And the header "Content-Type" should contain "text"
8 | And the header "Content-Type" should be equal to "text/html; charset=UTF-8"
9 | And the header "Content-Type" should not be equal to "x-test/no-such-type"
10 | And the header "Content-Type" should not contain "text/json"
11 | And the header "Content-Type" should match "@^text/html; [a-zA-Z=-]+@"
12 | And the header "Content-Type" should not match "/^no-such-type$/"
13 | And the header "xxx" should not exist
14 | And the response should expire in the future
15 | And the response should be encoded in "UTF-8"
16 |
17 | Scenario: Testing request methods.
18 | Given I send a GET request to "/rest/index.php"
19 | Then I should see "You have sent a GET request. "
20 | And I should see "No parameter received"
21 |
22 | When I send a GET request to "/rest/index.php?first=foo&second=bar"
23 | Then I should see "You have sent a GET request. "
24 | And I should see "2 parameter(s)"
25 | And I should see "first : foo"
26 | And I should see "second : bar"
27 |
28 | When I send a POST request to "/rest/index.php" with parameters:
29 | | key | value |
30 | | foo | bar |
31 | | foofile | @lorem.txt |
32 | Then I should see "You have sent a POST request. "
33 | And I should see "1 parameter(s)"
34 | And I should see "1 file(s)"
35 | And I should see "foo : bar"
36 | And I should see "foofile - name : lorem.txt"
37 | And I should see "foofile - error : 0"
38 | And I should see "foofile - size : 39"
39 |
40 | When I send a PUT request to "/rest/index.php"
41 | Then I should see "You have sent a PUT request. "
42 |
43 | When I send a DELETE request to "/rest/index.php"
44 | Then I should see "You have sent a DELETE request. "
45 |
46 | When I send a POST request to "/rest/index.php" with body:
47 | """
48 | This is a body.
49 | """
50 | Then I should see "Body : This is a body."
51 |
52 | When I send a PUT request to "/rest/index.php" with body:
53 | """
54 | {"this is":"some json"}
55 | """
56 | Then the response should be empty
57 |
58 | Scenario: request parameter with dot
59 | https://github.com/Behatch/contexts/issues/256
60 | When I send a POST request to "/rest/index.php" with parameters:
61 | | key | value |
62 | | item.id | 1 |
63 | Then I should see "item.id=1"
64 |
65 | Scenario: Add header
66 | Given I add "xxx" header equal to "yyy"
67 | When I send a GET request to "/rest/index.php"
68 | Then I should see "HTTP_XXX : yyy"
69 |
70 | Scenario: Add header with large numeric value
71 | Given I add "xxx-large-numeric" header equal to "92233720368547758070"
72 | When I send a GET request to "/rest/index.php"
73 | Then I should see "HTTP_XXX_LARGE_NUMERIC : 92233720368547758070"
74 |
75 | Scenario: Header should not be cross-scenarios persistent
76 | When I send a GET request to "/rest/index.php"
77 | Then I should not see "HTTP_XXX : yyy"
78 | Then I should not see "HTTP_XXX_LARGE_NUMERIC"
79 |
80 | Scenario: Case-insensitive header name
81 | Like describe in the rfc2614 §4.2
82 | https://tools.ietf.org/html/rfc2616#section-4.2
83 |
84 | When I send a GET request to "rest/index.php"
85 | Then the header "content-type" should contain "text"
86 |
87 | Scenario: Debug
88 | Given I add "xxx" header equal to "yyy"
89 | When I send a POST request to "/rest/index.php" with parameters:
90 | | key | value |
91 | | foo | bar |
92 | Then print last response headers
93 | And print the corresponding curl command
94 |
95 | Scenario: Response body
96 | Given I send a GET request to "/"
97 | Then the response should be equal to:
98 | """
99 | Congratulations, you've correctly set up your apache environment.
100 | """
101 |
102 | Scenario: Accept header should not be set by dfault
103 | When I send a GET request to "/rest/index.php"
104 | Then I should not see "HTTP_ACCEPT :"
105 |
106 | @>php5.5
107 | Scenario: Set content headers in POST request
108 | When I add "Content-Type" header equal to "xxx"
109 | When I send a "POST" request to "rest/index.php" with body:
110 | """
111 | {"name": "test"}
112 | """
113 | Then the response should contain ">CONTENT_TYPE : xxx"
114 | Then the response should contain ">HTTP_CONTENT_TYPE : xxx"
115 |
116 | Scenario: Content header is clear in different scenario
117 | When I send a "POST" request to "rest/index.php" with body:
118 | """
119 | {"name": "test"}
120 | """
121 | Then the response should not contain ">CONTENT_TYPE : xxx"
122 | Then the response should not contain ">HTTP_CONTENT_TYPE : xxx"
123 |
--------------------------------------------------------------------------------
/tests/features/ja/xml.feature:
--------------------------------------------------------------------------------
1 | #language: ja
2 | @japanese @xml
3 | フィーチャ: Testing XmlContext
4 |
5 | 背景:
6 | 前提 "/xml/feed.xml" を表示している
7 |
8 | シナリオ: Am I a XML ?
9 | ならば レスポンスがXML形式であること
10 | もし "/xml/feed.atom" を表示している
11 | ならば レスポンスがXML形式であること
12 | もし "/xml/feed.rss" を表示している
13 | ならば レスポンスがXML形式であること
14 | もし "/xml/book.xml" を表示している
15 | ならば レスポンスがXML形式であること
16 | もし "/xml/people.xml" を表示している
17 | ならば レスポンスがXML形式であること
18 | もし "/xml/country.xml" を表示している
19 | ならば レスポンスがXML形式であること
20 | もし "/xml/needsformatting.xml" を表示している
21 | ならば レスポンスがXMLであること
22 | もし "/xml/imnotaxml.xml" を表示している
23 | ならば レスポンスがXML形式でないこと
24 | もし "/xml/notfound.xml" を表示している
25 | ならば レスポンスがXMLでないこと
26 | # ならば ブレークポイントを設置する
27 |
28 | シナリオ: Validation with DTD
29 | ならば XMLフィードが自身のDTDに従っていること
30 |
31 | シナリオ: Validation with XSD file
32 | ならば XMLフィードがXSDファイル"tests/fixtures/www/xml/schema.xsd"に従っていること
33 |
34 | # ならば ブレークポイントを設置する
35 | シナリオ: Validation with inline XSD
36 | ならば XMLフィードが下記のXSDに従っていること:
37 | """
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | """
51 |
52 | シナリオ: Validation with relax NG file
53 | ならば XMLフィードがrelax NG schemaファイル"tests/fixtures/www/xml/schema.ng"に従っていること
54 |
55 | # ならば ブレークポイントを設置する
56 | シナリオ: Validation with inline relax NG
57 | ならば XMLフィードが下記のrelax NG schemaに従っていること:
58 | """
59 |
60 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | """
77 |
78 | シナリオ: Atom feed validation
79 | 前提 "/xml/feed.atom" を表示している
80 | ならば atomフィードが妥当であること
81 |
82 | シナリオ: RSS feed validation
83 | 前提 "/xml/feed.rss" を表示している
84 | ならば RSS2フィードが妥当であること
85 |
86 | # ならば ブレークポイントを設置する
87 | シナリオ: Check XML evaluation
88 | 前提 "/xml/book.xml" を表示している
89 | ならば XMLには "//book/chapter/title" 要素が存在していること
90 | かつ XMLには "//book/chapter/index" 要素が存在していないこと
91 | かつ XMLの "//book/chapter/title" 要素は "My books" と一致していること
92 | かつ XMLの "//book/title" 要素は "My wonderful lists" と一致していないこと
93 | かつ XMLの "//book/chapter/para/informaltable/tgroup" 要素には "cols" 属性が存在していること
94 | かつ XMLの "//book/chapter/title" 要素には "color" 属性が存在していないこと
95 | かつ XMLの "//book/chapter" 要素の "id" 属性は "books" と一致していること
96 | かつ XMLの "//book" 要素の "id" 属性は "choices" と一致していないこと
97 | かつ XMLには "//book/chapter/para/informaltable/tgroup/tbody" 要素を 3 個含んでいること
98 | かつ XMLの "//book/title" 要素は "is" を含んでいること
99 | かつ XMLの "//book/chapter/title" 要素は "if" を含んでいないこと
100 | # ならば ブレークポイントを設置する
101 |
102 | シナリオ: Check XML evaluation with namespaces and a default namespace
103 | 前提 "/xml/country.xml" を表示している
104 | ならば XMLは名前空間 "http://example.org/xsd/country" を使っていること
105 | かつ XMLには "//country/airports" 要素が存在していること
106 | かつ XMLには "//country/cities/city:city/city:park" 要素が存在していること
107 | かつ XMLには "//country/treasure" 要素が存在していないこと
108 | かつ XMLの "//city:city[@id=1]/city:park" 要素の "opened" 属性は "1873" と一致していること
109 | かつ XMLの "//city:city[@id=2]/city:park" 要素の "attraction" 属性は "Fireworks" と一致していないこと
110 | かつ XMLの "//country" 要素には "version" 属性が存在していること
111 | かつ XMLの "//country/airports/city:airport" 要素には "typo" 属性が存在していないこと
112 | かつ XMLには "//country/cities" 要素を 2 個含んでいること
113 | かつ XMLには "//country/cities/city:city[@id=2]" 要素を 1 個含んでいること
114 |
115 | シナリオ: Check XML evaluation with namespaces but no default namespace
116 | 前提 "/xml/people.xml" を表示している
117 | ならば XMLは名前空間 "http://example.org/ns" を使っていること
118 | かつ XMLは名前空間 "http://example.org/test" を使っていないこと
119 | かつ XMLには "//people" 要素が存在していること
120 | かつ XMLには "//people/p:person" 要素が存在していること
121 | かつ XMLには "//people/description" 要素が存在していないこと
122 | かつ XMLの "//people/p:person[@id=1]/items/item[@id=1]" 要素は "Rubber Ducky" と一致していること
123 | かつ XMLには "//people" 要素を 3 個含んでいること
124 | かつ XMLの "//people/p:person[@id=1]" 要素の "name" 属性は "Bert" と一致していること
125 | かつ XMLの "//people/p:person[@id=2]" 要素の "id" 属性は "4" と一致していないこと
126 | かつ XMLの "//people/p:person[@id=3]" 要素には "name" 属性が存在していること
127 | かつ XMLの "//people/p:person[@id=1]/items/item" 要素には "size" 属性が存在していないこと
128 |
129 | シナリオ: Pretty print xml
130 | 前提 "/xml/needsformatting.xml" を表示している
131 | かつ 最後のXMLレスポンスを表示する
132 |
--------------------------------------------------------------------------------
/tests/features/pt/xml.feature:
--------------------------------------------------------------------------------
1 | #language: pt
2 | @xml
3 | Funcionalidade: Testando o XmlContext
4 |
5 | Contexto:
6 | Quando Eu estou em "/xml/feed.xml"
7 |
8 | Cenário: Eu sou um XML?
9 | Então a resposta deve estar em XML
10 | Quando Eu estou em "/xml/feed.atom"
11 | Então a resposta deve estar em XML
12 | Quando Eu estou em "/xml/feed.rss"
13 | Então a resposta deve estar em XML
14 | Quando Eu estou em "/xml/book.xml"
15 | Então a resposta deve estar em XML
16 | Quando Eu estou em "/xml/people.xml"
17 | Então a resposta deve estar em XML
18 | Quando Eu estou em "/xml/country.xml"
19 | Então a resposta deve estar em XML
20 | Quando Eu estou em "/xml/needsformatting.xml"
21 | Então a resposta deve estar em XML
22 | Quando Eu estou em "/xml/imnotaxml.xml"
23 | Então a resposta não deve estar em XML
24 | Quando Eu estou em "/xml/notfound.xml"
25 | Então a resposta não deve estar em XML
26 |
27 | Cenário: Validação com DTD
28 | Então o XML deve ser válido de acordo com o seu DTD
29 |
30 | Cenário: Validação com um arquivo XSD
31 | Então o XML deve ser válido de acordo com o XSD "tests/fixtures/www/xml/schema.xsd"
32 |
33 | Cenário: Validação com um XSD inline
34 | Então o XML deve ser válido de acordo com esse XSD:
35 | """
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | """
49 |
50 | Cenário: Validação com um arquivo relax NG
51 | Então o XML deve ser válido de acordo com o schema relax NG "tests/fixtures/www/xml/schema.ng"
52 |
53 | Cenário: Validação com relax NG inline
54 | Então o XML deve ser válido de acordo com esse schema relax NG:
55 | """
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | """
74 |
75 | Cenário: Validação de feed Atom
76 | Quando Eu estou em "/xml/feed.atom"
77 | Então o feed atom deve ser válido
78 |
79 | Cenário: Validação de feed RSS
80 | Quando Eu estou em "/xml/feed.rss"
81 | Então o feed RSS2 deve ser válido
82 |
83 | Cenário: Verifica a interpretação do XML
84 | Quando Eu estou em "/xml/book.xml"
85 | Então o elemento XML "//book/chapter/title" deve existir
86 | E o elemento XML "//book/chapter/index" não deve existir
87 | E o elemento XML "//book/chapter/title" deve ser igual a "My books"
88 | E o elemento XML "//book/title" não deve ser igual a "My wonderful lists"
89 | E o elemento "//book/chapter/para/informaltable/tgroup" deve possuir o atributo XML "cols"
90 | E o elemento "//book/chapter/title" não deve possuir o atributo XML "color"
91 | E o atributo XML "id" no elemento "//book/chapter" deve ser igual a "books"
92 | E o atributo XML "id" no elemento "//book" não deve ser igual a "choices"
93 | E o elemento XML "//book/chapter/para/informaltable/tgroup/tbody" deve ter 3 elementos
94 | E o elemento XML "//book/title" deve conter "is"
95 | E o elemento XML "//book/chapter/title" não deve conter "if"
96 |
97 | Cenário: Verifica a interpretação do XML com namespaces e namespace default
98 | Quando Eu estou em "/xml/country.xml"
99 | Então o XML deve utilizar o namespace "http://example.org/xsd/country"
100 | E o elemento XML "//country/airports" deve existir
101 | E o elemento XML "//country/cities/city:city/city:park" deve existir
102 | E o elemento XML "//country/treasure" não deve existir
103 | E o atributo XML "opened" no elemento "//city:city[@id=1]/city:park" deve ser igual a "1873"
104 | E o atributo XML "attraction" no elemento "//city:city[@id=2]/city:park" não deve ser igual a "Fireworks"
105 | E o elemento "//country" deve possuir o atributo XML "version"
106 | E o elemento "//country/airports/city:airport" não deve possuir o atributo XML "typo"
107 | E o elemento XML "//country/cities" deve ter 2 elementos
108 | E o elemento XML "//country/cities/city:city[@id=2]" deve ter 1 elemento
109 |
110 | Cenário: Verifica a interpretação do XML com namespaces, mas sem namespace default
111 | Quando Eu estou em "/xml/people.xml"
112 | Então o XML deve utilizar o namespace "http://example.org/ns"
113 | E o XML não deve utilizar o namespace "http://example.org/test"
114 | E o elemento XML "//people" deve existir
115 | E o elemento XML "//people/p:person" deve existir
116 | E o elemento XML "//people/description" não deve existir
117 | E o elemento XML "//people/p:person[@id=1]/items/item[@id=1]" deve ser igual a "Rubber Ducky"
118 | E o elemento XML "//people" deve ter 3 elementos
119 | E o atributo XML "name" no elemento "//people/p:person[@id=1]" deve ser igual a "Bert"
120 | E o atributo XML "id" no elemento "//people/p:person[@id=2]" não deve ser igual a "4"
121 | E o elemento "//people/p:person[@id=3]" deve possuir o atributo XML "name"
122 | E o elemento "//people/p:person[@id=1]/items/item" não deve possuir o atributo XML "size"
123 |
124 | Cenário: Exibe o XML formatado
125 | Quando Eu estou em "/xml/needsformatting.xml"
126 | E exiba a última resposta XML
127 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - 'main'
7 | pull_request:
8 | branches:
9 | - 'main'
10 |
11 | env:
12 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 |
14 | jobs:
15 | php-cs-fixer:
16 | name: PHP-CS-Fixer
17 | runs-on: ubuntu-latest
18 | strategy:
19 | matrix:
20 | php:
21 | - '8.1'
22 | - '8.2'
23 | - '8.3'
24 | fail-fast: false
25 | steps:
26 | - name: Checkout
27 | uses: actions/checkout@v6
28 | - name: Setup PHP
29 | uses: shivammathur/setup-php@v2
30 | with:
31 | php-version: ${{ matrix.php }}
32 | extensions: intl, bcmath, curl, openssl, mbstring
33 | ini-values: memory_limit=-1
34 | tools: pecl, composer, php-cs-fixer
35 | coverage: none
36 | - name: Run PHP-CS-Fixer fix
37 | run: php-cs-fixer fix --dry-run --diff --ansi
38 |
39 | phpstan:
40 | name: PHPStan
41 | runs-on: ubuntu-latest
42 | strategy:
43 | matrix:
44 | php:
45 | - '8.1'
46 | - '8.2'
47 | - '8.3'
48 | fail-fast: false
49 | env:
50 | APP_DEBUG: '1' # https://github.com/phpstan/phpstan-symfony/issues/37
51 | steps:
52 | - name: Checkout
53 | uses: actions/checkout@v6
54 | - name: Setup PHP
55 | uses: shivammathur/setup-php@v2
56 | with:
57 | php-version: ${{ matrix.php }}
58 | extensions: intl, bcmath, curl, openssl, mbstring
59 | ini-values: memory_limit=-1
60 | tools: pecl, composer, phpstan
61 | coverage: none
62 | - name: Get composer cache directory
63 | id: composercache
64 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
65 | - name: Cache dependencies
66 | uses: actions/cache@v4
67 | with:
68 | path: ${{ steps.composercache.outputs.dir }}
69 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
70 | restore-keys: ${{ runner.os }}-composer-
71 | - name: Update project dependencies
72 | run: composer update --no-interaction --no-progress --ansi
73 | - name: Cache PHPStan results
74 | uses: actions/cache@v4
75 | with:
76 | path: /tmp/phpstan
77 | key: phpstan-php${{ matrix.php }}-${{ github.sha }}
78 | restore-keys: |
79 | phpstan-php${{ matrix.php }}-
80 | phpstan-
81 | continue-on-error: true
82 | - name: Run PHPStan analysis
83 | run: phpstan analyse --no-interaction --no-progress --no-interaction --ansi
84 |
85 | atoum:
86 | name: Atoum
87 | runs-on: ubuntu-latest
88 | strategy:
89 | matrix:
90 | php:
91 | - '8.1'
92 | - '8.2'
93 | - '8.3'
94 | fail-fast: false
95 | timeout-minutes: 20
96 | steps:
97 | - name: Checkout
98 | uses: actions/checkout@v6
99 | - name: Setup PHP
100 | uses: shivammathur/setup-php@v2
101 | with:
102 | php-version: ${{ matrix.php }}
103 | tools: pecl, composer
104 | extensions: intl, bcmath, curl, openssl, mbstring
105 | coverage: pcov
106 | ini-values: memory_limit=-1
107 | - name: Get composer cache directory
108 | id: composercache
109 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
110 | - name: Cache dependencies
111 | uses: actions/cache@v4
112 | with:
113 | path: ${{ steps.composercache.outputs.dir }}
114 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
115 | restore-keys: ${{ runner.os }}-composer-
116 | - name: Update project dependencies
117 | run: composer update --no-interaction --no-progress --ansi
118 | - name: Run tests
119 | run: ./bin/atoum
120 | # See https://github.com/SeleniumHQ/selenium/issues/9044
121 | behat:
122 | name: Behat
123 | runs-on: ubuntu-22.04
124 | strategy:
125 | matrix:
126 | php:
127 | - '8.1'
128 | profile:
129 | - 'default'
130 | - 'symfony2'
131 | fail-fast: false
132 | env:
133 | DISPLAY: ':99'
134 | steps:
135 | - uses: actions/checkout@v6
136 | - name: Setup PHP
137 | uses: shivammathur/setup-php@v2
138 | with:
139 | php-version: ${{ matrix.php }}
140 | tools: pecl, composer
141 | extensions: intl, bcmath, curl, openssl, mbstring
142 | coverage: pcov
143 | ini-values: memory_limit=-1,display_errors=1,error_reporting=-1
144 | - name: Setup Java
145 | uses: actions/setup-java@v3
146 | with:
147 | distribution: temurin
148 | java-version: '8'
149 | - name: Run Selenium
150 | run: |
151 | wget "https://selenium-release.storage.googleapis.com/3.9/selenium-server-standalone-3.9.1.jar" -O selenium.jar
152 | java -jar selenium.jar -debug &> /dev/null &
153 | - name: Run PHP fixtures server
154 | run: php -S localhost:8080 -t tests/fixtures/www &> /dev/null &
155 | - name: Get composer cache directory
156 | id: composercache
157 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
158 | - name: Cache dependencies
159 | uses: actions/cache@v4
160 | with:
161 | path: ${{ steps.composercache.outputs.dir }}
162 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
163 | restore-keys: ${{ runner.os }}-composer-
164 | - name: Update project dependencies
165 | run: composer update --no-interaction --no-progress --ansi
166 | - name: Wait for browser & PHP to start
167 | run: |
168 | while ! nc -z localhost 4444 root = $root;
19 | }
20 |
21 | public static function getTranslationResources()
22 | {
23 | return glob(__DIR__.'/../../i18n/*.xliff');
24 | }
25 |
26 | /**
27 | * Execute a command.
28 | *
29 | * @Given (I )execute :command
30 | */
31 | public function iExecute($cmd): void
32 | {
33 | $start = microtime(true);
34 |
35 | exec($cmd, $this->output, $this->lastReturnCode);
36 |
37 | $this->lastExecutionTime = microtime(true) - $start;
38 | }
39 |
40 | /**
41 | * Execute a command from project root.
42 | *
43 | * @Given (I )execute :command from project root
44 | */
45 | public function iExecuteFromProjectRoot($cmd): void
46 | {
47 | $cmd = $this->root.\DIRECTORY_SEPARATOR.$cmd;
48 | $this->iExecute($cmd);
49 | }
50 |
51 | /**
52 | * Display the last command output.
53 | *
54 | * @Then (I )display the last command output
55 | */
56 | public function iDumpCommandOutput(): void
57 | {
58 | echo implode(\PHP_EOL, $this->output);
59 | }
60 |
61 | /**
62 | * Command should succeed.
63 | *
64 | * @Then command should succeed
65 | */
66 | public function commandShouldSucceed(): void
67 | {
68 | if (0 !== $this->lastReturnCode) {
69 | throw new \Exception(\sprintf('Command should succeed %b', $this->lastReturnCode));
70 | }
71 | }
72 |
73 | /**
74 | * Command should fail.
75 | *
76 | * @Then command should fail
77 | */
78 | public function commandShouldFail(): void
79 | {
80 | if (0 === $this->lastReturnCode) {
81 | throw new \Exception(\sprintf('Command should fail %b', $this->lastReturnCode));
82 | }
83 | }
84 |
85 | /**
86 | * Command should last less than.
87 | *
88 | * @Then command should last less than :seconds seconds
89 | */
90 | public function commandShouldLastLessThan($seconds): void
91 | {
92 | if ($this->lastExecutionTime > $seconds) {
93 | throw new \Exception(\sprintf('Last command last %s which is more than %s seconds', $this->lastExecutionTime, $seconds));
94 | }
95 | }
96 |
97 | /**
98 | * Command should last more than.
99 | *
100 | * @Then command should last more than :seconds seconds
101 | */
102 | public function commandShouldMoreLessThan($seconds): void
103 | {
104 | if ($this->lastExecutionTime < $seconds) {
105 | throw new \Exception(\sprintf('Last command last %s which is less than %s seconds', $this->lastExecutionTime, $seconds));
106 | }
107 | }
108 |
109 | /**
110 | * Checks, that output contains specified text.
111 | *
112 | * @Then output should contain :text
113 | */
114 | public function outputShouldContain($text): void
115 | {
116 | $regex = '~'.$text.'~ui';
117 |
118 | $check = false;
119 | foreach ($this->output as $line) {
120 | if (1 === preg_match($regex, $line)) {
121 | $check = true;
122 | break;
123 | }
124 | }
125 |
126 | if (false === $check) {
127 | throw new \Exception(\sprintf("The text '%s' was not found anywhere on output of command.\n%s", $text, implode("\n", $this->output)));
128 | }
129 | }
130 |
131 | /**
132 | * Checks, that output not contains specified text.
133 | *
134 | * @Then output should not contain :text
135 | */
136 | public function outputShouldNotContain($text): void
137 | {
138 | $regex = '~'.$text.'~ui';
139 |
140 | foreach ($this->output as $line) {
141 | if (1 === preg_match($regex, $line)) {
142 | throw new \Exception(\sprintf("The text '%s' was found somewhere on output of command.\n%s", $text, implode("\n", $this->output)));
143 | }
144 | }
145 | }
146 |
147 | /**
148 | * @Given output should be:
149 | */
150 | public function outputShouldBe(PyStringNode $string): void
151 | {
152 | $expected = $string->getStrings();
153 | foreach ($this->output as $index => $line) {
154 | if ($line !== $expected[$index]) {
155 | throw new \Exception(\sprintf("instead of\n%s", implode("\n", $this->output)));
156 | }
157 | }
158 | }
159 |
160 | /**
161 | * @Given output should not be:
162 | */
163 | public function outputShouldNotBe(PyStringNode $string): void
164 | {
165 | $expected = $string->getStrings();
166 |
167 | $check = false;
168 | foreach ($this->output as $index => $line) {
169 | if ($line !== $expected[$index]) {
170 | $check = true;
171 | break;
172 | }
173 | }
174 |
175 | if (false === $check) {
176 | throw new \Exception('Output should not be');
177 | }
178 | }
179 |
180 | /**
181 | * @Given (I )create the file :filename containing:
182 | * @Given (I )create the file :filename contening:
183 | */
184 | public function iCreateTheFileContaining($filename, PyStringNode $string): void
185 | {
186 | if (!is_file($filename)) {
187 | file_put_contents($filename, $string);
188 | $this->createdFiles[] = $filename;
189 | } else {
190 | throw new \RuntimeException("'$filename' already exists.");
191 | }
192 | }
193 |
194 | /**
195 | * @Then print the content of :filename file
196 | */
197 | public function printTheContentOfFile($filename): void
198 | {
199 | if (is_file($filename)) {
200 | echo file_get_contents($filename);
201 | } else {
202 | throw new \RuntimeException("'$filename' doesn't exists.");
203 | }
204 | }
205 |
206 | /**
207 | * @AfterScenario
208 | */
209 | public function after(): void
210 | {
211 | foreach ($this->createdFiles as $filename) {
212 | unlink($filename);
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/tests/features/ru/xml.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | @xml
3 | Функционал: Тестирование XmlContext
4 |
5 | Контекст:
6 | Пусть я на странице "/xml/feed.xml"
7 |
8 | Сценарий: Я XML ?
9 | Тогда ответ должен быть в XML
10 | Когда я на странице "/xml/feed.atom"
11 | Тогда ответ должен быть в XML
12 | Когда я на странице "/xml/feed.rss"
13 | Тогда ответ должен быть в XML
14 | Когда я на странице "/xml/book.xml"
15 | Тогда ответ должен быть в XML
16 | Когда я на странице "/xml/people.xml"
17 | Тогда ответ должен быть в XML
18 | Когда я на странице "/xml/country.xml"
19 | Тогда ответ должен быть в XML
20 | Когда я на странице "/xml/needsformatting.xml"
21 | Тогда ответ должен быть в XML
22 | Когда я на странице "/xml/imnotaxml.xml"
23 | Тогда ответ не должен быть в XML
24 | Когда я на странице "/xml/notfound.xml"
25 | Тогда ответ не должен быть в XML
26 |
27 | Сценарий: Валидация с DTD
28 | Тогда XML должен соответствовать его DTD
29 |
30 | Сценарий: Валидация с XSD файлом
31 | Тогда XML должен соответствовать XSD "tests/fixtures/www/xml/schema.xsd"
32 |
33 | Сценарий: Валидация с XSD
34 | Тогда XML должен соответствовать следующему XSD:
35 | """
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | """
49 |
50 | Сценарий: Валидация с relax NG файлом
51 | Тогда XML должен соответствовать relax NG схеме "tests/fixtures/www/xml/schema.ng"
52 |
53 | Сценарий: Валидация с relax NG
54 | Тогда XML должен соответствовать следующей relax NG схеме:
55 | """
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | """
74 |
75 | Сценарий: Валидация Atom
76 | Пусть я на странице "/xml/feed.atom"
77 | Тогда atom должен быть валидным
78 |
79 | Сценарий: Валидация RSS
80 | Пусть я на странице "/xml/feed.rss"
81 | Тогда RSS2 должен быть валидным
82 |
83 | Сценарий: Тестирование разбора XML
84 | Пусть я на странице "/xml/book.xml"
85 | Тогда XML элемент "//book/chapter/title" должен существовать
86 | И XML элемент "//book/chapter/index" не должен существовать
87 | И XML элемент "//book/chapter/title" должен быть равен "My books"
88 | И XML элемент "//book/title" не должен быть равен "My wonderful lists"
89 | И XML атрибут "cols" у элемента "//book/chapter/para/informaltable/tgroup" должен существовать
90 | И XML атрибут "color" у элемента "//book/chapter/title" не должен существовать
91 | И XML атрибут "id" у элемента "//book/chapter" должен быть равен "books"
92 | И XML атрибут "id" у элемента "//book" не должен быть равен "choices"
93 | И XML элемент "//book/chapter/para/informaltable/tgroup/tbody" должен содержать 3 элемента
94 | И XML элемент "//book/title" должен содержать "is"
95 | И XML элемент "//book/chapter/title" не должен содержать "if"
96 |
97 | Сценарий: Тестирование разбора XML с пространствами имён и пространством имён по умолчанию
98 | Пусть я на странице "/xml/country.xml"
99 | Тогда XML должен использовать пространство имён "http://example.org/xsd/country"
100 | И XML элемент "//country/airports" должен существовать
101 | И XML элемент "//country/cities/city:city/city:park" должен существовать
102 | И XML элемент "//country/treasure" не должен существовать
103 | И XML атрибут "opened" у элемента "//city:city[@id=1]/city:park" должен быть равен "1873"
104 | И XML атрибут "attraction" у элемента "//city:city[@id=2]/city:park" не должен быть равен "Fireworks"
105 | И XML атрибут "version" у элемента "//country" должен существовать
106 | И XML атрибут "typo" у элемента "//country/airports/city:airport" не должен существовать
107 | И XML элемент "//country/cities" должен содержать 2 элемента
108 | И XML элемент "//country/cities/city:city[@id=2]" должен содержать 1 элемент
109 |
110 | Сценарий: Тестирование разбора XML с пространствами имён, но без пространства имён по умолчанию
111 | Пусть я на странице "/xml/people.xml"
112 | Тогда XML должен использовать пространство имён "http://example.org/ns"
113 | И XML не должен использовать пространство имён "http://example.org/test"
114 | И XML элемент "//people" должен существовать
115 | И XML элемент "//people/p:person" должен существовать
116 | И XML элемент "//people/description" не должен существовать
117 | И XML элемент "//people/p:person[@id=1]/items/item[@id=1]" должен быть равен "Rubber Ducky"
118 | И XML элемент "//people" должен содержать 3 элемента
119 | И XML атрибут "name" у элемента "//people/p:person[@id=1]" должен быть равен "Bert"
120 | И XML атрибут "id" у элемента "//people/p:person[@id=2]" не должен быть равен "4"
121 | И XML атрибут "name" у элемента "//people/p:person[@id=3]" должен существовать
122 | И XML атрибут "size" у элемента "//people/p:person[@id=1]/items/item" не должен существовать
123 |
124 | Сценарий: Красивый вывод XML
125 | Пусть я на странице "/xml/needsformatting.xml"
126 | И выведи последний XML ответ
127 |
--------------------------------------------------------------------------------
/tests/features/en/xml.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | @xml
3 | Feature: Testing XmlContext
4 |
5 | Background:
6 | Given I am on "/xml/feed.xml"
7 |
8 | Scenario: Am I a XML ?
9 | Then the response should be in XML
10 | When I am on "/xml/feed.atom"
11 | Then the response should be in XML
12 | When I am on "/xml/feed.rss"
13 | Then the response should be in XML
14 | When I am on "/xml/book.xml"
15 | Then the response should be in XML
16 | When I am on "/xml/people.xml"
17 | Then the response should be in XML
18 | When I am on "/xml/country.xml"
19 | Then the response should be in XML
20 | When I am on "/xml/needsformatting.xml"
21 | Then the response should be in XML
22 | When I am on "/xml/imnotaxml.xml"
23 | Then the response should not be in XML
24 | When I am on "/xml/notfound.xml"
25 | Then the response should not be in XML
26 |
27 | Scenario: Validation with DTD
28 | Then the XML feed should be valid according to its DTD
29 |
30 | Scenario: Validation with XSD file
31 | Then the XML feed should be valid according to the XSD "tests/fixtures/www/xml/schema.xsd"
32 |
33 | Scenario: Validation with inline XSD
34 | Then the XML feed should be valid according to this XSD:
35 | """
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | """
49 |
50 | Scenario: Validation with relax NG file
51 | Then the XML feed should be valid according to the relax NG schema "tests/fixtures/www/xml/schema.ng"
52 |
53 | Scenario: Validation with inline relax NG
54 | Then the XML feed should be valid according to this relax NG schema:
55 | """
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | """
74 |
75 | Scenario: Atom feed validation
76 | Given I am on "/xml/feed.atom"
77 | Then the atom feed should be valid
78 |
79 | Scenario: RSS feed validation
80 | Given I am on "/xml/feed.rss"
81 | Then the RSS2 feed should be valid
82 |
83 | Scenario: Check XML evaluation
84 | Given I am on "/xml/book.xml"
85 | Then the XML element "//book/chapter/title" should exist
86 | And the XML element "//book/chapter/index" should not exist
87 | And the XML element "//book/chapter/title" should be equal to "My books"
88 | And the XML element "//book/title" should not be equal to "My wonderful lists"
89 | And the XML attribute "cols" on element "//book/chapter/para/informaltable/tgroup" should exist
90 | And the XML attribute "color" on element "//book/chapter/title" should not exist
91 | And the XML attribute "id" on element "//book/chapter" should be equal to "books"
92 | And the XML attribute "id" on element "//book" should not be equal to "choices"
93 | And the XML element "//book/chapter/para/informaltable/tgroup/tbody" should have 3 elements
94 | And the XML element "//book/title" should contain "is"
95 | And the XML element "//book/chapter/title" should not contain "if"
96 |
97 | Scenario: Check XML evaluation with namespaces and a default namespace
98 | Given I am on "/xml/country.xml"
99 | Then the XML should use the namespace "http://example.org/xsd/country"
100 | And the XML element "//country/airports" should exist
101 | And the XML element "//country/cities/city:city/city:park" should exist
102 | And the XML element "//country/treasure" should not exist
103 | And the XML attribute "opened" on element "//city:city[@id=1]/city:park" should be equal to "1873"
104 | And the XML attribute "attraction" on element "//city:city[@id=2]/city:park" should not be equal to "Fireworks"
105 | And the XML attribute "version" on element "//country" should exist
106 | And the XML attribute "typo" on element "//country/airports/city:airport" should not exist
107 | And the XML element "//country/cities" should have 2 elements
108 | And the XML element "//country/cities/city:city[@id=2]" should have 1 element
109 |
110 | Scenario: Check XML evaluation with namespaces but no default namespace
111 | Given I am on "/xml/people.xml"
112 | Then the XML should use the namespace "http://example.org/ns"
113 | And the XML should not use the namespace "http://example.org/test"
114 | And the XML element "//people" should exist
115 | And the XML element "//people/p:person" should exist
116 | And the XML element "//people/description" should not exist
117 | And the XML element "//people/p:person[@id=1]/items/item[@id=1]" should be equal to "Rubber Ducky"
118 | And the XML element "//people" should have 3 elements
119 | And the XML attribute "name" on element "//people/p:person[@id=1]" should be equal to "Bert"
120 | And the XML attribute "id" on element "//people/p:person[@id=2]" should not be equal to "4"
121 | And the XML attribute "name" on element "//people/p:person[@id=3]" should exist
122 | And the XML attribute "size" on element "//people/p:person[@id=1]/items/item" should not exist
123 |
124 | Scenario: Pretty print xml
125 | Given I am on "/xml/needsformatting.xml"
126 | And print last XML response
127 |
--------------------------------------------------------------------------------
/tests/units/Context/JsonContext.php:
--------------------------------------------------------------------------------
1 | httpCallResultPool = new HttpCallResultPool();
16 | $this->httpCallResultPool->store(
17 | new HttpCallResult(json_encode([
18 | 'a string node' => 'some string',
19 | 'another string node' => 'some other string',
20 | 'a null node' => null,
21 | 'a true node' => true,
22 | 'a false node' => false,
23 | 'a number node' => 3,
24 | 'an array node' => [
25 | 'one',
26 | 'two',
27 | 'three',
28 | ],
29 | ], \JSON_THROW_ON_ERROR))
30 | );
31 | }
32 |
33 | public function testTheJsonNodeShouldBeEqualTo(): void
34 | {
35 | $this
36 | ->given($this->newTestedInstance($this->httpCallResultPool))
37 | ->then
38 |
39 | ->if($this->testedInstance->theJsonNodeShouldBeEqualTo('a string node', 'some string'))
40 |
41 | ->exception(function (): void {
42 | $this->testedInstance->theJsonNodeShouldBeEqualTo('a string node', 'expectedstring');
43 | })
44 | ->hasMessage("The node 'a string node' value is '\"some string\"', 'expectedstring' expected");
45 | }
46 |
47 | public function testTheJsonNodesShouldBeEqualTo(): void
48 | {
49 | $this
50 | ->given($this->newTestedInstance($this->httpCallResultPool))
51 | ->and($validTableNode = new TableNode([
52 | 1 => ['a string node', 'some string'],
53 | 2 => ['another string node', 'some other string'],
54 | ]))
55 | ->then
56 |
57 | ->if($this->testedInstance->theJsonNodesShouldBeEqualTo($validTableNode))
58 |
59 | ->exception(function (): void {
60 | $invalidTableNode = new TableNode([
61 | 1 => ['a string node', 'may the force'],
62 | 2 => ['another string node', 'be with you'],
63 | ]);
64 | $this->testedInstance->theJsonNodesShouldBeEqualTo($invalidTableNode);
65 | })
66 | ->hasMessage("The node 'a string node' value is '\"some string\"', 'may the force' expected\nThe node 'another string node' value is '\"some other string\"', 'be with you' expected");
67 | }
68 |
69 | public function testTheJsonNodeShouldMatch(): void
70 | {
71 | $this
72 | ->given($this->newTestedInstance($this->httpCallResultPool))
73 | ->then
74 |
75 | ->if($this->testedInstance->theJsonNodeShouldMatch('a string node', '/some/'))
76 |
77 | ->exception(function (): void {
78 | $this->testedInstance->theJsonNodeShouldMatch('a string node', '/nomatch/');
79 | })
80 | ->hasMessage("The node 'a string node' value is '\"some string\"', '/nomatch/' pattern expected");
81 | }
82 |
83 | public function testTheJsonNodeShouldBeNull(): void
84 | {
85 | $this
86 | ->given($this->newTestedInstance($this->httpCallResultPool))
87 | ->then
88 |
89 | ->if($this->testedInstance->theJsonNodeShouldBeNull('a null node'))
90 |
91 | ->exception(function (): void {
92 | $this->testedInstance->theJsonNodeShouldBeNull('a string node');
93 | })
94 | ->hasMessage("The node 'a string node' value is '\"some string\"', null expected");
95 | }
96 |
97 | public function testTheJsonNodeShouldNotBeNull(): void
98 | {
99 | $this
100 | ->given($this->newTestedInstance($this->httpCallResultPool))
101 | ->then
102 |
103 | ->if($this->testedInstance->theJsonNodeShouldNotBeNull('a string node'))
104 |
105 | ->exception(function (): void {
106 | $this->testedInstance->theJsonNodeShouldNotBeNull('a null node');
107 | })
108 | ->hasMessage("The node 'a null node' value is null, non-null value expected");
109 | }
110 |
111 | public function testTheJsonNodeShouldBeTrue(): void
112 | {
113 | $this
114 | ->given($this->newTestedInstance($this->httpCallResultPool))
115 | ->then
116 |
117 | ->if($this->testedInstance->theJsonNodeShouldBeTrue('a true node'))
118 |
119 | ->exception(function (): void {
120 | $this->testedInstance->theJsonNodeShouldBeTrue('a false node');
121 | })
122 | ->hasMessage("The node 'a false node' value is 'false', 'true' expected");
123 | }
124 |
125 | public function testTheJsonNodeShouldBeFalse(): void
126 | {
127 | $this
128 | ->given($this->newTestedInstance($this->httpCallResultPool))
129 | ->then
130 |
131 | ->if($this->testedInstance->theJsonNodeShouldBeFalse('a false node'))
132 |
133 | ->exception(function (): void {
134 | $this->testedInstance->theJsonNodeShouldBeFalse('a true node');
135 | })
136 | ->hasMessage("The node 'a true node' value is 'true', 'false' expected");
137 | }
138 |
139 | public function testTheJsonNodeShouldBeEqualToTheString(): void
140 | {
141 | $this
142 | ->given($this->newTestedInstance($this->httpCallResultPool))
143 | ->then
144 |
145 | ->if($this->testedInstance->theJsonNodeShouldBeEqualToTheString('a string node', 'some string'))
146 |
147 | ->exception(function (): void {
148 | $this->testedInstance->theJsonNodeShouldBeEqualToTheString('a string node', 'expected');
149 | })
150 | ->hasMessage("The node 'a string node' value is '\"some string\"', string 'expected' expected");
151 | }
152 |
153 | public function testTheJsonNodeShouldBeEqualToTheNumber(): void
154 | {
155 | $this
156 | ->given($this->newTestedInstance($this->httpCallResultPool))
157 | ->then
158 |
159 | ->if($this->testedInstance->theJsonNodeShouldBeEqualToTheNumber('a number node', 3))
160 |
161 | ->exception(function (): void {
162 | $this->testedInstance->theJsonNodeShouldBeEqualToTheNumber('a number node', 2);
163 | })
164 | ->hasMessage("The node 'a number node' value is '3', number '2' expected");
165 | }
166 |
167 | public function testTheJsonNodeShouldExist(): void
168 | {
169 | $this
170 | ->given($this->newTestedInstance($this->httpCallResultPool))
171 | ->then
172 |
173 | ->if($this->testedInstance->theJsonNodeShouldExist('a string node'))
174 |
175 | ->exception(function (): void {
176 | $this->testedInstance->theJsonNodeShouldExist('invalid key');
177 | })
178 | ->hasMessage("The node 'invalid key' does not exist.");
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/tests/features/ru/json.feature:
--------------------------------------------------------------------------------
1 | #language: ru
2 | @json
3 | Функционал: Тестирование JSONContext
4 |
5 | Сценарий: Я JSON ?
6 | Пусть я на странице "/json/imajson.json"
7 | Тогда ответ должен быть в JSON
8 | Когда я на странице "/json/emptyarray.json"
9 | Тогда ответ должен быть в JSON
10 | Когда я на странице "/json/emptyobject.json"
11 | Тогда ответ должен быть в JSON
12 | Когда я на странице "/json/imnotajson.json"
13 | Тогда ответ не должен быть в JSON
14 |
15 | Сценарий: Подсчёт элементов JSON
16 | Пусть я на странице "/json/imajson.json"
17 | Тогда узел JSON "numbers" должен содержать 4 элемента
18 |
19 | Сценарий: Тестирование разбора JSON
20 | Пусть я на странице "/json/imajson.json"
21 |
22 | Тогда узел JSON "foo" должен существовать
23 | И узел JSON "root.foo" должен существовать
24 | И узел JSON "foo" должен содержать "bar"
25 | И узел JSON "foo" не должен содержать "something else"
26 |
27 | И узел JSON "numbers[0]" должен содержать "öne"
28 | И узел JSON "numbers[1]" должен содержать "two"
29 | И узел JSON "numbers[2]" должен содержать "three"
30 | И узел JSON "numbers[3].complexeshizzle" должен быть равен "true"
31 | И узел JSON "numbers[3].so[0]" должен быть равен "very"
32 | И узел JSON "numbers[3].so[1].complicated" должен быть равен "indeed"
33 | И узел JSON "numbers[0]" должен соответствовать "/ö.{1}e/"
34 | И узел JSON "numbers[1]" должен соответствовать "/.{2}o/"
35 | И узел JSON "numbers[2]" должен соответствовать "/[a-z]{3}e.+/"
36 |
37 | И узлы JSON должны быть равны:
38 | | foo | bar |
39 | | numbers[0] | öne |
40 | | numbers[1] | two |
41 | | numbers[2] | three |
42 |
43 | И узлы JSON должны содержать:
44 | | foo | bar |
45 | | numbers[0] | öne |
46 | | numbers[1] | two |
47 | | numbers[2] | three |
48 |
49 | И узлы JSON не должны содержать:
50 | | foo | something else |
51 |
52 | И узел JSON "bar" не должен существовать
53 |
54 | Сценарий: Валидация Json схемой
55 | Пусть я на странице "/json/imajson.json"
56 | Тогда JSON должен соответствовать схеме "tests/fixtures/www/json/schema.json"
57 |
58 | Сценарий: Валидация Json схемой со ссылкой (случай невалидного JSON)
59 | Пусть я на странице "/json/withref-invalid.json"
60 | Тогда JSON не должен соответствовать схеме "tests/fixtures/www/json/schemaref.json"
61 |
62 | Сценарий: Валидация Json схемой со ссылкой
63 | Пусть я на странице "/json/withref.json"
64 | Тогда JSON должен соответствовать схеме "tests/fixtures/www/json/schemaref.json"
65 |
66 | Сценарий: Валидация Json
67 | Пусть я на странице "/json/imajson.json"
68 | Тогда JSON должен соответствовать следующей схеме:
69 | """
70 | {
71 | "type": "object",
72 | "$schema": "http://json-schema.org/draft-03/schema",
73 | "required":true,
74 | "properties": {
75 | "foo": {
76 | "type": "string",
77 | "required":true
78 | },
79 | "numbers": {
80 | "type": "array",
81 | "required":true,
82 | "öne": {
83 | "type": "string",
84 | "required":true
85 | },
86 | "two": {
87 | "type": "string",
88 | "required":true
89 | },
90 | "three": {
91 | "type": "string",
92 | "required":true
93 | }
94 | }
95 | }
96 | }
97 | """
98 |
99 | Сценарий: Глубокая валидация Json
100 | Пусть я на странице "/json/booking.json"
101 | Тогда JSON не должен соответствовать следующей схеме:
102 | """
103 | {
104 | "type":"object",
105 | "$schema": "http://json-schema.org/draft-03/schema",
106 | "required":false,
107 | "properties":{
108 | "Booking": {
109 | "type":"object",
110 | "required":false
111 | },
112 | "Metadata": {
113 | "type":"object",
114 | "required":false,
115 | "properties":{
116 | "First": {
117 | "type":"object",
118 | "required":false,
119 | "properties":{
120 | "default_value": {
121 | "type":"boolean",
122 | "required":false
123 | },
124 | "enabled": {
125 | "type":"boolean",
126 | "required":true
127 | }
128 | }
129 | }
130 | }
131 | }
132 | }
133 | }
134 | """
135 |
136 | Сценарий: Валидация содержимого Json
137 | Пусть я на странице "/json/imajson.json"
138 | Тогда JSON должен быть равен:
139 | """
140 | {
141 | "foo": "bar",
142 | "numbers": [
143 | "öne",
144 | "two",
145 | "three",
146 | {
147 | "complexeshizzle": true,
148 | "so": [
149 | "very",
150 | {
151 | "complicated": "indeed"
152 | }
153 | ]
154 | }
155 | ]
156 | }
157 | """
158 | И выведи последний JSON ответ
159 |
160 | Сценарий: Проверка корневого узла JSON
161 | Пусть я на странице "/json/rootarray.json"
162 | Тогда ответ должен быть в JSON
163 | И узел JSON "root[0].name" должен существовать
164 | И узел JSON "root" должен содержать 2 элемента
165 |
166 | Сценарий: Тестирование сравнения типов
167 | Пусть я на странице "/json/arraywithtypes.json"
168 | Тогда ответ должен быть в JSON
169 | И узел JSON "root[0]" должен быть null
170 | И узел JSON "root[1]" должен быть истиной
171 | И узел JSON "root[2]" должен быть ложью
172 | И узел JSON "root[3]" должен быть равен строке "dunglas.fr"
173 | И узел JSON "root[4]" должен быть равен числу 1312
174 | И узел JSON "root[4]" должен быть равен числу 1312.0
175 | И узел JSON "root[5]" должен быть равен числу 1936.2
176 |
177 | Сценарий: Тестирование не-null значений
178 | Пусть я на странице "/json/notnullvalues.json"
179 | Тогда ответ должен быть в JSON
180 | И узел JSON '' должен содержать 5 элементов
181 | И узел JSON "one" не должен быть null
182 | И узел JSON "one" должен быть ложью
183 | И узел JSON "two" не должен быть null
184 | И узел JSON "two" должен быть истиной
185 | И узел JSON "three" не должен быть null
186 | И узел JSON "three" должен быть равен строке ""
187 | И узел JSON "four" не должен быть null
188 | И узел JSON "four" должен быть равен строке "foo"
189 | И узел JSON "five" не должен быть null
190 | И узел JSON "five" должен быть равен числу 5
191 |
--------------------------------------------------------------------------------
/tests/features/en/json.feature:
--------------------------------------------------------------------------------
1 | # language: en
2 | @json
3 | Feature: Testing JSONContext
4 |
5 | Scenario: Am I a JSON ?
6 | Given I am on "/json/imajson.json"
7 | Then the response should be in JSON
8 | When I am on "/json/emptyarray.json"
9 | Then the response should be in JSON
10 | When I am on "/json/emptyobject.json"
11 | Then the response should be in JSON
12 | When I am on "/json/imnotajson.json"
13 | Then the response should not be in JSON
14 |
15 | Scenario: Count JSON elements
16 | Given I am on "/json/imajson.json"
17 | Then the JSON node "numbers" should have 4 elements
18 |
19 | Scenario: Checking JSON evaluation
20 | Given I am on "/json/imajson.json"
21 |
22 | Then the JSON node "foo" should exist
23 | And the JSON node "root.foo" should exist
24 | And the JSON node "foo" should contain "bar"
25 | And the JSON node "foo" should not contain "something else"
26 |
27 | And the JSON node "numbers[0]" should contain "öne"
28 | And the JSON node "numbers[1]" should contain "two"
29 | And the JSON node "numbers[2]" should contain "three"
30 | And the JSON node "numbers[3].complexeshizzle" should be equal to "true"
31 | And the JSON node "numbers[3].so[0]" should be equal to "very"
32 | And the JSON node "numbers[3].so[1].complicated" should be equal to "indeed"
33 | And the JSON node "numbers[0]" should match "/ö.{1}e/"
34 | And the JSON node "numbers[1]" should match "/.{2}o/"
35 | And the JSON node "numbers[2]" should match "/[a-z]{3}e.+/"
36 |
37 | And the JSON nodes should be equal to:
38 | | foo | bar |
39 | | numbers[0] | öne |
40 | | numbers[1] | two |
41 | | numbers[2] | three |
42 |
43 | And the JSON nodes should contain:
44 | | foo | bar |
45 | | numbers[0] | öne |
46 | | numbers[1] | two |
47 | | numbers[2] | three |
48 |
49 | And the JSON nodes should not contain:
50 | | foo | something else |
51 |
52 | And the JSON node "bar" should not exist
53 |
54 | Scenario: Json validation with schema
55 | Given I am on "/json/imajson.json"
56 | Then the JSON should be valid according to the schema "tests/fixtures/www/json/schema.json"
57 |
58 | Scenario: Json validation with schema containing ref (invalid case)
59 | Given I am on "/json/withref-invalid.json"
60 | Then the JSON should be invalid according to the schema "tests/fixtures/www/json/schemaref.json"
61 |
62 | Scenario: Json validation with schema containing ref
63 | Given I am on "/json/withref.json"
64 | Then the JSON should be valid according to the schema "tests/fixtures/www/json/schemaref.json"
65 |
66 | Scenario: Json validation
67 | Given I am on "/json/imajson.json"
68 | Then the JSON should be valid according to this schema:
69 | """
70 | {
71 | "type": "object",
72 | "$schema": "http://json-schema.org/draft-03/schema",
73 | "required":true,
74 | "properties": {
75 | "foo": {
76 | "type": "string",
77 | "required":true
78 | },
79 | "numbers": {
80 | "type": "array",
81 | "required":true,
82 | "one": {
83 | "type": "string",
84 | "required":true
85 | },
86 | "two": {
87 | "type": "string",
88 | "required":true
89 | },
90 | "three": {
91 | "type": "string",
92 | "required":true
93 | }
94 | }
95 | }
96 | }
97 | """
98 |
99 | Scenario: Json validation deep
100 | Given I am on "/json/booking.json"
101 | Then the JSON should be invalid according to this schema:
102 | """
103 | {
104 | "type":"object",
105 | "$schema": "http://json-schema.org/draft-03/schema",
106 | "required":false,
107 | "properties":{
108 | "Booking": {
109 | "type":"object",
110 | "required":false
111 | },
112 | "Metadata": {
113 | "type":"object",
114 | "required":false,
115 | "properties":{
116 | "First": {
117 | "type":"object",
118 | "required":false,
119 | "properties":{
120 | "default_value": {
121 | "type":"boolean",
122 | "required":false
123 | },
124 | "enabled": {
125 | "type":"boolean",
126 | "required":true
127 | }
128 | }
129 | }
130 | }
131 | }
132 | }
133 | }
134 | """
135 |
136 | Scenario: Json contents validation
137 | Given I am on "/json/imajson.json"
138 | Then the JSON should be equal to:
139 | """
140 | {
141 | "foo": "bar",
142 | "numbers": [
143 | "öne",
144 | "two",
145 | "three",
146 | {
147 | "complexeshizzle": true,
148 | "so": [
149 | "very",
150 | {
151 | "complicated": "indeed"
152 | }
153 | ]
154 | }
155 | ]
156 | }
157 | """
158 | And print last JSON response
159 |
160 | Scenario: Check json root node
161 | Given I am on "/json/rootarray.json"
162 | Then the response should be in JSON
163 | And the JSON node "root[0].name" should exist
164 | And the JSON node "root" should have 2 elements
165 |
166 | Scenario: Check with type comparison
167 | Given I am on "/json/arraywithtypes.json"
168 | Then the response should be in JSON
169 | And the JSON node "root[0]" should be null
170 | And the JSON node "root[1]" should be true
171 | And the JSON node "root[2]" should be false
172 | And the JSON node "root[3]" should be equal to the string "dunglas.fr"
173 | And the JSON node "root[4]" should be equal to the number 1312
174 | And the JSON node "root[4]" should be equal to the number 1312.0
175 | And the JSON node "root[5]" should be equal to the number 1936.2
176 |
177 | Scenario: Check not null values
178 | Given I am on "/json/notnullvalues.json"
179 | Then the response should be in JSON
180 | And the JSON node '' should have 5 elements
181 | And the JSON node "one" should not be null
182 | And the JSON node "one" should be false
183 | And the JSON node "two" should not be null
184 | And the JSON node "two" should be true
185 | And the JSON node "three" should not be null
186 | And the JSON node "three" should be equal to the string ""
187 | And the JSON node "four" should not be null
188 | And the JSON node "four" should be equal to the string "foo"
189 | And the JSON node "five" should not be null
190 | And the JSON node "five" should be equal to the number 5
191 |
192 | Scenario: Json validation against swagger dump file
193 | Given I am on "/json/swaggerpartial.json"
194 | Then the response should be in JSON
195 | And the JSON should be valid according to swagger "tests/fixtures/www/json/swagger.json" dump schema "sample-definition"
196 |
197 | Scenario: Json validation against swagger dump file
198 | Given I am on "/json/swaggerpartial.json"
199 | Then the response should be in JSON
200 | And the JSON should not be valid according to swagger "tests/fixtures/www/json/swagger.json" dump schema "sample-invalid-definition"
201 |
--------------------------------------------------------------------------------