| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549 |
- <?php
- // +----------------------------------------------------------------------
- // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
- // +----------------------------------------------------------------------
- // | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
- // +----------------------------------------------------------------------
- // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
- // +----------------------------------------------------------------------
- // | Author: yunwuxin <448901948@qq.com>
- // +----------------------------------------------------------------------
- namespace think\testing;
- use Exception;
- use InvalidArgumentException;
- use PHPUnit\Framework\ExpectationFailedException as PHPUnitException;
- use Symfony\Component\DomCrawler\Crawler;
- use Symfony\Component\DomCrawler\Form;
- use think\facade\Request;
- use think\facade\Url;
- use think\File;
- use think\helper\Str;
- use think\response\Redirect;
- trait InteractsWithPages
- {
- /** @var \Symfony\Component\DomCrawler\Crawler */
- protected $crawler;
- /**
- * All of the stored inputs for the current page.
- *
- * @var array
- */
- protected $inputs = [];
- /**
- * All of the stored uploads for the current page.
- *
- * @var array
- */
- protected $uploads = [];
- public function visit($uri)
- {
- return $this->makeRequest('GET', $uri);
- }
- protected function submitForm($buttonText, $inputs = [], $uploads = [])
- {
- $this->makeRequestUsingForm($this->fillForm($buttonText, $inputs), $uploads);
- return $this;
- }
- protected function see($text, $negate = false)
- {
- $method = $negate ? 'assertNotRegExp' : 'assertRegExp';
- $rawPattern = preg_quote($text, '/');
- $escapedPattern = preg_quote(htmlentities($text, ENT_QUOTES, 'UTF-8', false), '/');
- $pattern = $rawPattern == $escapedPattern
- ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
- $this->$method("/$pattern/i", $this->response->getContent());
- return $this;
- }
- protected function notSee($text)
- {
- return $this->see($text, true);
- }
- public function seeInElement($element, $text, $negate = false)
- {
- if ($negate) {
- return $this->notSeeInElement($element, $text);
- }
- $this->assertTrue(
- $this->hasInElement($element, $text),
- "Element [$element] should contain the expected text [{$text}]"
- );
- return $this;
- }
- public function notSeeInElement($element, $text)
- {
- $this->assertFalse(
- $this->hasInElement($element, $text),
- "Element [$element] should not contain the expected text [{$text}]"
- );
- return $this;
- }
- public function seeLink($text, $url = null)
- {
- $message = "No links were found with expected text [{$text}]";
- if ($url) {
- $message .= " and URL [{$url}]";
- }
- $this->assertTrue($this->hasLink($text, $url), "{$message}.");
- return $this;
- }
- public function notSeeLink($text, $url = null)
- {
- $message = "A link was found with expected text [{$text}]";
- if ($url) {
- $message .= " and URL [{$url}]";
- }
- $this->assertFalse($this->hasLink($text, $url), "{$message}.");
- return $this;
- }
- protected function hasLink($text, $url = null)
- {
- $links = $this->crawler->selectLink($text);
- if ($links->count() == 0) {
- return false;
- }
- // If the URL is null, we assume the developer only wants to find a link
- // with the given text regardless of the URL. So, if we find the link
- // we will return true now. Otherwise, we look for the given URL.
- if (null == $url) {
- return true;
- }
- $url = $this->addRootToRelativeUrl($url);
- /** @var \DOMElement $link */
- foreach ($links as $link) {
- if ($link->getAttribute('href') == $url) {
- return true;
- }
- }
- return false;
- }
- protected function addRootToRelativeUrl($url)
- {
- if (!Str::startsWith($url, ['http', 'https'])) {
- return Url::build($url);
- }
- return $url;
- }
- public function seeInField($selector, $expected)
- {
- $this->assertSame(
- $expected, $this->getInputOrTextAreaValue($selector),
- "The field [{$selector}] does not contain the expected value [{$expected}]."
- );
- return $this;
- }
- public function notSeeInField($selector, $value)
- {
- $this->assertNotSame(
- $this->getInputOrTextAreaValue($selector), $value,
- "The input [{$selector}] should not contain the value [{$value}]."
- );
- return $this;
- }
- public function seeIsChecked($selector)
- {
- $this->assertTrue(
- $this->isChecked($selector),
- "The checkbox [{$selector}] is not checked."
- );
- return $this;
- }
- public function notSeeIsChecked($selector)
- {
- $this->assertFalse(
- $this->isChecked($selector),
- "The checkbox [{$selector}] is checked."
- );
- return $this;
- }
- protected function isChecked($selector)
- {
- $checkbox = $this->filterByNameOrId($selector, "input[type='checkbox']");
- if ($checkbox->count() == 0) {
- throw new Exception("There are no checkbox elements with the name or ID [$selector].");
- }
- return $checkbox->attr('checked') !== null;
- }
- public function seeIsSelected($selector, $expected)
- {
- $this->assertEquals(
- $expected, $this->getSelectedValue($selector),
- "The field [{$selector}] does not contain the selected value [{$expected}]."
- );
- return $this;
- }
- public function notSeeIsSelected($selector, $value)
- {
- $this->assertNotEquals(
- $value, $this->getSelectedValue($selector),
- "The field [{$selector}] contains the selected value [{$value}]."
- );
- return $this;
- }
- protected function getSelectedValue($selector)
- {
- $field = $this->filterByNameOrId($selector);
- if ($field->count() == 0) {
- throw new Exception("There are no elements with the name or ID [$selector].");
- }
- $element = $field->nodeName();
- if ('select' == $element) {
- return $this->getSelectedValueFromSelect($field);
- }
- if ('input' == $element) {
- return $this->getCheckedValueFromRadioGroup($field);
- }
- throw new Exception("Given selector [$selector] is not a select or radio group.");
- }
- protected function getSelectedValueFromSelect(Crawler $field)
- {
- if ($field->nodeName() !== 'select') {
- throw new Exception('Given element is not a select element.');
- }
- /** @var \DOMElement $option */
- foreach ($field->children() as $option) {
- if ($option->hasAttribute('selected')) {
- return $option->getAttribute('value');
- }
- }
- }
- protected function getCheckedValueFromRadioGroup(Crawler $radioGroup)
- {
- if ($radioGroup->nodeName() !== 'input' || $radioGroup->attr('type') !== 'radio') {
- throw new Exception('Given element is not a radio button.');
- }
- /** @var \DOMElement $radio */
- foreach ($radioGroup as $radio) {
- if ($radio->hasAttribute('checked')) {
- return $radio->getAttribute('value');
- }
- }
- }
- protected function click($name)
- {
- $link = $this->crawler->selectLink($name);
- if (!count($link)) {
- $link = $this->filterByNameOrId($name, 'a');
- if (!count($link)) {
- throw new InvalidArgumentException(
- "Could not find a link with a body, name, or ID attribute of [{$name}]."
- );
- }
- }
- $this->visit($link->link()->getUri());
- return $this;
- }
- protected function type($text, $element)
- {
- return $this->storeInput($element, $text);
- }
- protected function check($element)
- {
- return $this->storeInput($element, true);
- }
- protected function uncheck($element)
- {
- return $this->storeInput($element, false);
- }
- protected function select($option, $element)
- {
- return $this->storeInput($element, $option);
- }
- protected function attach($absolutePath, $element)
- {
- $this->uploads[$element] = $absolutePath;
- return $this->storeInput($element, $absolutePath);
- }
- protected function press($buttonText)
- {
- return $this->submitForm($buttonText, $this->inputs, $this->uploads);
- }
- protected function getInputOrTextAreaValue($selector)
- {
- $field = $this->filterByNameOrId($selector, ['input', 'textarea']);
- if ($field->count() == 0) {
- throw new Exception("There are no elements with the name or ID [$selector].");
- }
- $element = $field->nodeName();
- if ('input' == $element) {
- return $field->attr('value');
- }
- if ('textarea' == $element) {
- return $field->text();
- }
- throw new Exception("Given selector [$selector] is not an input or textarea.");
- }
- protected function seePageIs($uri)
- {
- $this->assertPageLoaded($uri = $this->prepareUrlForRequest($uri));
- $this->assertEquals(
- $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
- );
- return $this;
- }
- protected function hasInElement($element, $text)
- {
- $elements = $this->crawler->filter($element);
- $rawPattern = preg_quote($text, '/');
- $escapedPattern = preg_quote(htmlentities($text, ENT_QUOTES, 'UTF-8', false), '/');
- $pattern = $rawPattern == $escapedPattern
- ? $rawPattern : "({$rawPattern}|{$escapedPattern})";
- foreach ($elements as $element) {
- $element = new Crawler($element);
- if (preg_match("/$pattern/i", $element->html())) {
- return true;
- }
- }
- return false;
- }
- protected function storeInput($element, $text)
- {
- $this->assertFilterProducesResults($element);
- $element = str_replace('#', '', $element);
- $this->inputs[$element] = $text;
- return $this;
- }
- protected function assertFilterProducesResults($filter)
- {
- $crawler = $this->filterByNameOrId($filter);
- if (!count($crawler)) {
- throw new InvalidArgumentException(
- "Nothing matched the filter [{$filter}] CSS query provided for [{$this->currentUri}]."
- );
- }
- }
- protected function fillForm($buttonText, $inputs = [])
- {
- if (!is_string($buttonText)) {
- $inputs = $buttonText;
- $buttonText = null;
- }
- return $this->getForm($buttonText)->setValues($inputs);
- }
- protected function getForm($buttonText = null)
- {
- try {
- if ($buttonText) {
- return $this->crawler->selectButton($buttonText)->form();
- }
- return $this->crawler->filter('form')->form();
- } catch (InvalidArgumentException $e) {
- throw new InvalidArgumentException(
- "Could not find a form that has submit button [{$buttonText}]."
- );
- }
- }
- protected function filterByNameOrId($name, $elements = '*')
- {
- $name = str_replace('#', '', $name);
- $id = str_replace(['[', ']'], ['\\[', '\\]'], $name);
- $elements = is_array($elements) ? $elements : [$elements];
- array_walk($elements, function (&$element) use ($name, $id) {
- $element = "{$element}#{$id}, {$element}[name='{$name}']";
- });
- return $this->crawler->filter(implode(', ', $elements));
- }
- protected function makeRequest($method, $uri, $parameters = [], $cookies = [], $files = [])
- {
- $uri = $this->prepareUrlForRequest($uri);
- $this->call($method, $uri, $parameters, $cookies, $files);
- $this->clearInputs()->followRedirects()->assertPageLoaded($uri);
- $this->currentUri = Request::url(true);
- $this->crawler = new Crawler($this->response->getContent(), $this->currentUri);
- return $this;
- }
- protected function makeRequestUsingForm(Form $form, array $uploads = [])
- {
- $files = $this->convertUploadsForTesting($form, $uploads);
- return $this->makeRequest(
- $form->getMethod(), $form->getUri(), $this->extractParametersFromForm($form), [], $files
- );
- }
- protected function assertPageLoaded($uri, $message = null)
- {
- $status = $this->response->getCode();
- try {
- $this->assertEquals(200, $status);
- } catch (PHPUnitException $e) {
- $message = $message ?: "A request to [{$uri}] failed. Received status code [{$status}].";
- throw new HttpException($message);
- }
- }
- protected function convertUploadsForTesting(Form $form, array $uploads)
- {
- $files = $form->getFiles();
- $names = array_keys($files);
- $files = array_map(function (array $file, $name) use ($uploads) {
- return isset($uploads[$name])
- ? $this->getUploadedFileForTesting($file, $uploads, $name)
- : $file;
- }, $files, $names);
- return array_combine($names, $files);
- }
- protected function getUploadedFileForTesting($file, $uploads, $name)
- {
- $file['name'] = basename($uploads[$name]);
- return new File(
- $file['tmp_name'], $file, true
- );
- }
- protected function extractParametersFromForm(Form $form)
- {
- parse_str(http_build_query($form->getValues()), $parameters);
- return $parameters;
- }
- protected function clearInputs()
- {
- $this->inputs = [];
- $this->uploads = [];
- return $this;
- }
- protected function followRedirects()
- {
- while ($this->response instanceof Redirect) {
- $this->makeRequest('GET', $this->response->getTargetUrl());
- }
- return $this;
- }
- protected function prepareUrlForRequest($uri)
- {
- if (Str::startsWith($uri, '/')) {
- $uri = substr($uri, 1);
- }
- if (!Str::startsWith($uri, 'http')) {
- $uri = $this->baseUrl . '/' . $uri;
- }
- return trim($uri, '/');
- }
- }
|