PhpBrowserTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. <?php
  2. use Codeception\Util\Stub;
  3. require_once 'tests/data/app/data.php';
  4. require_once __DIR__ . '/TestsForBrowsers.php';
  5. use GuzzleHttp\Psr7\Response;
  6. class PhpBrowserTest extends TestsForBrowsers
  7. {
  8. /**
  9. * @var \Codeception\Module\PhpBrowser
  10. */
  11. protected $module;
  12. protected $history = [];
  13. protected function setUp()
  14. {
  15. $this->module = new \Codeception\Module\PhpBrowser(make_container());
  16. $url = 'http://localhost:8000';
  17. $this->module->_setConfig(array('url' => $url));
  18. $this->module->_initialize();
  19. $this->module->_cleanup();
  20. $this->module->_before($this->makeTest());
  21. if (class_exists('GuzzleHttp\Url')) {
  22. $this->history = new \GuzzleHttp\Subscriber\History();
  23. $this->module->guzzle->getEmitter()->attach($this->history);
  24. } else {
  25. $this->module->guzzle->getConfig('handler')->push(\GuzzleHttp\Middleware::history($this->history));
  26. }
  27. }
  28. private function getLastRequest()
  29. {
  30. if (is_array($this->history)) {
  31. return end($this->history)['request'];
  32. } else {
  33. return $this->history->getLastRequest();
  34. }
  35. }
  36. protected function tearDown()
  37. {
  38. if ($this->module) {
  39. $this->module->_after($this->makeTest());
  40. }
  41. data::clean();
  42. }
  43. protected function makeTest()
  44. {
  45. return Stub::makeEmpty('\Codeception\Test\Cept');
  46. }
  47. public function testAjax()
  48. {
  49. $this->module->amOnPage('/');
  50. $this->module->sendAjaxGetRequest('/info');
  51. $this->assertNotNull(data::get('ajax'));
  52. $this->module->sendAjaxPostRequest('/form/complex', array('show' => 'author'));
  53. $this->assertNotNull(data::get('ajax'));
  54. $post = data::get('form');
  55. $this->assertEquals('author', $post['show']);
  56. }
  57. public function testLinksWithNonLatin()
  58. {
  59. $this->module->amOnPage('/info');
  60. $this->module->seeLink('Ссылочка');
  61. $this->module->click('Ссылочка');
  62. }
  63. public function testSetMultipleCookies()
  64. {
  65. $this->module->amOnPage('/');
  66. $cookie_name_1 = 'test_cookie';
  67. $cookie_value_1 = 'this is a test';
  68. $this->module->setCookie($cookie_name_1, $cookie_value_1);
  69. $cookie_name_2 = '2_test_cookie';
  70. $cookie_value_2 = '2 this is a test';
  71. $this->module->setCookie($cookie_name_2, $cookie_value_2);
  72. $this->module->seeCookie($cookie_name_1);
  73. $this->module->seeCookie($cookie_name_2);
  74. $this->module->dontSeeCookie('evil_cookie');
  75. $cookie1 = $this->module->grabCookie($cookie_name_1);
  76. $this->assertEquals($cookie_value_1, $cookie1);
  77. $cookie2 = $this->module->grabCookie($cookie_name_2);
  78. $this->assertEquals($cookie_value_2, $cookie2);
  79. $this->module->resetCookie($cookie_name_1);
  80. $this->module->dontSeeCookie($cookie_name_1);
  81. $this->module->seeCookie($cookie_name_2);
  82. $this->module->resetCookie($cookie_name_2);
  83. $this->module->dontSeeCookie($cookie_name_2);
  84. }
  85. public function testSessionsHaveIndependentCookies()
  86. {
  87. $this->module->amOnPage('/');
  88. $cookie_name_1 = 'test_cookie';
  89. $cookie_value_1 = 'this is a test';
  90. $this->module->setCookie($cookie_name_1, $cookie_value_1);
  91. $session = $this->module->_backupSession();
  92. $this->module->_initializeSession();
  93. $this->module->dontSeeCookie($cookie_name_1);
  94. $cookie_name_2 = '2_test_cookie';
  95. $cookie_value_2 = '2 this is a test';
  96. $this->module->setCookie($cookie_name_2, $cookie_value_2);
  97. $this->module->_loadSession($session);
  98. $this->module->dontSeeCookie($cookie_name_2);
  99. $this->module->seeCookie($cookie_name_1);
  100. }
  101. public function testSubmitFormGet()
  102. {
  103. $I = $this->module;
  104. $I->amOnPage('/search');
  105. $I->submitForm('form', array('searchQuery' => 'test'));
  106. $I->see('Success');
  107. }
  108. public function testHtmlRedirect()
  109. {
  110. $this->module->amOnPage('/redirect2');
  111. $this->module->seeResponseCodeIs(200);
  112. $this->module->seeCurrentUrlEquals('/info');
  113. $this->module->amOnPage('/redirect_interval');
  114. $this->module->seeCurrentUrlEquals('/redirect_interval');
  115. }
  116. public function testHtmlRedirectWithParams()
  117. {
  118. $this->module->amOnPage('/redirect_params');
  119. $this->module->seeResponseCodeIs(200);
  120. $this->module->seeCurrentUrlEquals('/search?one=1&two=2');
  121. }
  122. public function testMetaRefresh()
  123. {
  124. $this->module->amOnPage('/redirect_meta_refresh');
  125. $this->module->seeResponseCodeIs(200);
  126. $this->module->seeCurrentUrlEquals('/info');
  127. }
  128. public function testMetaRefreshIsIgnoredIfIntervalIsLongerThanMaxInterval()
  129. {
  130. // prepare config
  131. $config = $this->module->_getConfig();
  132. $config['refresh_max_interval'] = 3; // less than 9
  133. $this->module->_reconfigure($config);
  134. $this->module->amOnPage('/redirect_meta_refresh');
  135. $this->module->seeResponseCodeIs(200);
  136. $this->module->seeCurrentUrlEquals('/redirect_meta_refresh');
  137. }
  138. public function testRefreshRedirect()
  139. {
  140. $this->module->amOnPage('/redirect3');
  141. $this->module->seeResponseCodeIs(200);
  142. $this->module->seeCurrentUrlEquals('/info');
  143. $this->module->amOnPage('/redirect_header_interval');
  144. $this->module->seeCurrentUrlEquals('/redirect_header_interval');
  145. $this->module->see('Welcome to test app!');
  146. }
  147. public function testRedirectWithGetParams()
  148. {
  149. $this->module->amOnPage('/redirect4');
  150. $this->module->seeInCurrentUrl('/search?ln=test@gmail.com&sn=testnumber');
  151. $params = data::get('params');
  152. $this->assertContains('test@gmail.com', $params);
  153. }
  154. public function testRedirectBaseUriHasPath()
  155. {
  156. // prepare config
  157. $config = $this->module->_getConfig();
  158. $config['url'] .= '/somepath'; // append path to the base url
  159. $this->module->_reconfigure($config);
  160. $this->module->amOnPage('/redirect_base_uri_has_path');
  161. $this->module->seeResponseCodeIs(200);
  162. $this->module->seeCurrentUrlEquals('/somepath/info');
  163. $this->module->see('Lots of valuable data here');
  164. }
  165. public function testRedirectBaseUriHasPathAnd302Code()
  166. {
  167. // prepare config
  168. $config = $this->module->_getConfig();
  169. $config['url'] .= '/somepath'; // append path to the base url
  170. $this->module->_reconfigure($config);
  171. $this->module->amOnPage('/redirect_base_uri_has_path_302');
  172. $this->module->seeResponseCodeIs(200);
  173. $this->module->seeCurrentUrlEquals('/somepath/info');
  174. $this->module->see('Lots of valuable data here');
  175. }
  176. public function testRelativeRedirect()
  177. {
  178. // test relative redirects where the effective request URI is in a
  179. // subdirectory
  180. $this->module->amOnPage('/relative/redirect');
  181. $this->module->seeResponseCodeIs(200);
  182. $this->module->seeCurrentUrlEquals('/relative/info');
  183. // also, test relative redirects where the effective request URI is not
  184. // in a subdirectory
  185. $this->module->amOnPage('/relative_redirect');
  186. $this->module->seeResponseCodeIs(200);
  187. $this->module->seeCurrentUrlEquals('/info');
  188. }
  189. public function testChainedRedirects()
  190. {
  191. $this->module->amOnPage('/redirect_twice');
  192. $this->module->seeResponseCodeIs(200);
  193. $this->module->seeCurrentUrlEquals('/info');
  194. }
  195. public function testDisabledRedirects()
  196. {
  197. $this->module->client->followRedirects(false);
  198. $this->module->amOnPage('/redirect_twice');
  199. $this->module->seeResponseCodeIs(302);
  200. $this->module->seeCurrentUrlEquals('/redirect_twice');
  201. }
  202. public function testRedirectLimitReached()
  203. {
  204. $this->module->client->setMaxRedirects(1);
  205. try {
  206. $this->module->amOnPage('/redirect_twice');
  207. $this->assertTrue(false, 'redirect limit is not respected');
  208. } catch (\LogicException $e) {
  209. $this->assertEquals(
  210. 'The maximum number (1) of redirections was reached.',
  211. $e->getMessage(),
  212. 'redirect limit is respected'
  213. );
  214. }
  215. }
  216. public function testRedirectLimitNotReached()
  217. {
  218. $this->module->client->setMaxRedirects(2);
  219. $this->module->amOnPage('/redirect_twice');
  220. $this->module->seeResponseCodeIs(200);
  221. $this->module->seeCurrentUrlEquals('/info');
  222. }
  223. public function testLocationHeaderDoesNotRedirectWhenStatusCodeIs201()
  224. {
  225. $this->module->amOnPage('/location_201');
  226. $this->module->seeResponseCodeIs(201);
  227. $this->module->seeCurrentUrlEquals('/location_201');
  228. }
  229. public function testSetCookieByHeader()
  230. {
  231. $this->module->amOnPage('/cookies2');
  232. $this->module->seeResponseCodeIs(200);
  233. $this->module->seeCookie('a');
  234. $this->assertEquals('b', $this->module->grabCookie('a'));
  235. $this->module->seeCookie('c');
  236. }
  237. public function testUrlSlashesFormatting()
  238. {
  239. $this->module->amOnPage('somepage.php');
  240. $this->module->seeCurrentUrlEquals('/somepage.php');
  241. $this->module->amOnPage('///somepage.php');
  242. $this->module->seeCurrentUrlEquals('/somepage.php');
  243. }
  244. public function testSettingContentTypeFromHtml()
  245. {
  246. $this->module->amOnPage('/content-iso');
  247. $charset = $this->module->client->getResponse()->getHeader('Content-Type');
  248. $this->assertEquals('text/html;charset=ISO-8859-1', $charset);
  249. }
  250. public function testSettingCharsetFromHtml()
  251. {
  252. $this->module->amOnPage('/content-cp1251');
  253. $charset = $this->module->client->getResponse()->getHeader('Content-Type');
  254. $this->assertEquals('text/html;charset=windows-1251', $charset);
  255. }
  256. /**
  257. * @Issue https://github.com/Codeception/Codeception/issues/933
  258. */
  259. public function testSubmitFormWithQueries()
  260. {
  261. $this->module->amOnPage('/form/example3');
  262. $this->module->seeElement('form');
  263. $this->module->submitForm('form', array(
  264. 'name' => 'jon',
  265. ));
  266. $form = data::get('form');
  267. $this->assertEquals('jon', $form['name']);
  268. $this->module->seeCurrentUrlEquals('/form/example3?validate=yes');
  269. }
  270. public function testHeadersByConfig()
  271. {
  272. $this->module->_setConfig(['headers' => ['xxx' => 'yyyy']]);
  273. $this->module->_initialize();
  274. $this->module->amOnPage('/form1');
  275. if (method_exists($this->module->guzzle, 'getConfig')) {
  276. $headers = $this->module->guzzle->getConfig('headers');
  277. } else {
  278. $headers = $this->module->guzzle->getDefaultOption('headers');
  279. }
  280. $this->assertArrayHasKey('xxx', $headers);
  281. }
  282. public function testHeadersBySetHeader()
  283. {
  284. $this->module->setHeader('xxx', 'yyyy');
  285. $this->module->amOnPage('/');
  286. $this->assertTrue($this->getLastRequest()->hasHeader('xxx'));
  287. }
  288. public function testDeleteHeaders()
  289. {
  290. $this->module->setHeader('xxx', 'yyyy');
  291. $this->module->deleteHeader('xxx');
  292. $this->module->amOnPage('/');
  293. $this->assertFalse($this->getLastRequest()->hasHeader('xxx'));
  294. }
  295. public function testDeleteHeadersByEmptyValue()
  296. {
  297. $this->module->setHeader('xxx', 'yyyy');
  298. $this->module->setHeader('xxx', '');
  299. $this->module->amOnPage('/');
  300. $this->assertFalse($this->getLastRequest()->hasHeader('xxx'));
  301. }
  302. public function testCurlOptions()
  303. {
  304. $this->module->_setConfig(array('url' => 'http://google.com', 'curl' => array('CURLOPT_NOBODY' => true)));
  305. $this->module->_initialize();
  306. if (method_exists($this->module->guzzle, 'getConfig')) {
  307. $config = $this->module->guzzle->getConfig();
  308. } else {
  309. $config = $this->module->guzzle->getDefaultOption('config');
  310. }
  311. $this->assertArrayHasKey('curl', $config);
  312. $this->assertArrayHasKey(CURLOPT_NOBODY, $config['curl']);
  313. }
  314. public function testCurlSslOptions()
  315. {
  316. $this->module->_setConfig(array(
  317. 'url' => 'https://google.com',
  318. 'curl' => array(
  319. 'CURLOPT_NOBODY' => true,
  320. 'CURLOPT_SSL_CIPHER_LIST' => 'TLSv1',
  321. )));
  322. $this->module->_initialize();
  323. if (method_exists($this->module->guzzle, 'getConfig')) {
  324. $config = $this->module->guzzle->getConfig();
  325. } else {
  326. $config = $this->module->guzzle->getDefaultOption('config');
  327. }
  328. $this->assertArrayHasKey('curl', $config);
  329. $this->assertArrayHasKey(CURLOPT_SSL_CIPHER_LIST, $config['curl']);
  330. $this->module->amOnPage('/');
  331. $this->assertSame('', $this->module->_getResponseContent(), 'CURLOPT_NOBODY setting is not respected');
  332. }
  333. public function testHttpAuth()
  334. {
  335. $this->module->amOnPage('/auth');
  336. $this->module->seeResponseCodeIs(401);
  337. $this->module->see('Unauthorized');
  338. $this->module->amHttpAuthenticated('davert', 'password');
  339. $this->module->amOnPage('/auth');
  340. $this->module->seeResponseCodeIs(200);
  341. $this->module->dontSee('Unauthorized');
  342. $this->module->see("Welcome, davert");
  343. $this->module->amHttpAuthenticated(null, null);
  344. $this->module->amOnPage('/auth');
  345. $this->module->seeResponseCodeIs(401);
  346. $this->module->amHttpAuthenticated('davert', '123456');
  347. $this->module->amOnPage('/auth');
  348. $this->module->see('Forbidden');
  349. }
  350. public function testRawGuzzle()
  351. {
  352. $code = $this->module->executeInGuzzle(function (\GuzzleHttp\Client $client) {
  353. $res = $client->get('/info');
  354. return $res->getStatusCode();
  355. });
  356. $this->assertEquals(200, $code);
  357. }
  358. /**
  359. * If we have a form with fields like
  360. * ```
  361. * <input type="file" name="foo" />
  362. * <input type="file" name="foo[bar]" />
  363. * ```
  364. * then only array variable will be used while simple variable will be ignored in php $_FILES
  365. * (eg $_FILES = [
  366. * foo => [
  367. * tmp_name => [
  368. * 'bar' => 'asdf'
  369. * ],
  370. * //...
  371. * ]
  372. * ]
  373. * )
  374. * (notice there is no entry for file "foo", only for file "foo[bar]"
  375. * this will check if current element contains inner arrays within it's keys
  376. * so we can ignore element itself and only process inner files
  377. */
  378. public function testFormWithFilesInOnlyArray()
  379. {
  380. $this->shouldFail();
  381. $this->module->amOnPage('/form/example13');
  382. $this->module->attachFile('foo', 'app/avatar.jpg');
  383. $this->module->attachFile('foo[bar]', 'app/avatar.jpg');
  384. $this->module->click('Submit');
  385. }
  386. public function testDoubleSlash()
  387. {
  388. $I = $this->module;
  389. $I->amOnPage('/register');
  390. $I->submitForm('form', array('test' => 'test'));
  391. $formUrl = $this->module->client->getHistory()->current()->getUri();
  392. $formPath = parse_url($formUrl)['path'];
  393. $this->assertEquals($formPath, '/register');
  394. }
  395. public function testFillFieldWithoutPage()
  396. {
  397. $this->setExpectedException("\\Codeception\\Exception\\ModuleException");
  398. $this->module->fillField('#name', 'Nothing special');
  399. }
  400. public function testArrayFieldSubmitForm()
  401. {
  402. $this->skipForOldGuzzle();
  403. $this->module->amOnPage('/form/example17');
  404. $this->module->submitForm(
  405. 'form',
  406. [
  407. 'FooBar' => ['bar' => 'booze'],
  408. 'Food' => [
  409. 'beer' => [
  410. 'yum' => ['yeah' => 'crunked']
  411. ]
  412. ]
  413. ]
  414. );
  415. $data = data::get('form');
  416. $this->assertEquals('booze', $data['FooBar']['bar']);
  417. $this->assertEquals('crunked', $data['Food']['beer']['yum']['yeah']);
  418. }
  419. public function testCookiesForDomain()
  420. {
  421. $this->skipForOldGuzzle();
  422. $mock = new \GuzzleHttp\Handler\MockHandler([
  423. new Response(200, ['X-Foo' => 'Bar']),
  424. ]);
  425. $handler = \GuzzleHttp\HandlerStack::create($mock);
  426. $handler->push(\GuzzleHttp\Middleware::history($this->history));
  427. $client = new \GuzzleHttp\Client(['handler' => $handler, 'base_uri' => 'http://codeception.com']);
  428. $guzzleConnector = new \Codeception\Lib\Connector\Guzzle6();
  429. $guzzleConnector->setClient($client);
  430. $guzzleConnector->getCookieJar()->set(new \Symfony\Component\BrowserKit\Cookie('hello', 'world'));
  431. $guzzleConnector->request('GET', 'http://codeception.com/');
  432. $this->assertArrayHasKey('cookies', $this->history[0]['options']);
  433. /** @var $cookie GuzzleHttp\Cookie\SetCookie **/
  434. $cookies = $this->history[0]['options']['cookies']->toArray();
  435. $cookie = reset($cookies);
  436. $this->assertEquals('codeception.com', $cookie['Domain']);
  437. }
  438. /**
  439. * @issue https://github.com/Codeception/Codeception/issues/2653
  440. */
  441. public function testSetCookiesByOptions()
  442. {
  443. $config = $this->module->_getConfig();
  444. $config['cookies'] = [
  445. [
  446. 'Name' => 'foo',
  447. 'Value' => 'bar1',
  448. ],
  449. [
  450. 'Name' => 'baz',
  451. 'Value' => 'bar2',
  452. ],
  453. ];
  454. $this->module->_reconfigure($config);
  455. // this url redirects if cookies are present
  456. $this->module->amOnPage('/cookies');
  457. $this->module->seeCurrentUrlEquals('/info');
  458. }
  459. private function skipForOldGuzzle()
  460. {
  461. if (class_exists('GuzzleHttp\Url')) {
  462. $this->markTestSkipped("Not for Guzzle <6");
  463. }
  464. }
  465. /**
  466. * @issue https://github.com/Codeception/Codeception/issues/2234
  467. */
  468. public function testEmptyValueOfCookie()
  469. {
  470. //set cookie
  471. $this->module->amOnPage('/cookies2');
  472. $this->module->amOnPage('/unset-cookie');
  473. $this->module->seeResponseCodeIs(200);
  474. $this->module->dontSeeCookie('a');
  475. }
  476. public function testRequestApi()
  477. {
  478. $this->setExpectedException('Codeception\Exception\ModuleException');
  479. $response = $this->module->_request('POST', '/form/try', ['user' => 'davert']);
  480. $data = data::get('form');
  481. $this->assertEquals('davert', $data['user']);
  482. $this->assertInternalType('string', $response);
  483. $this->assertContains('Welcome to test app', $response);
  484. $this->module->click('Welcome to test app'); // page not loaded
  485. }
  486. public function testLoadPageApi()
  487. {
  488. $this->module->_loadPage('POST', '/form/try', ['user' => 'davert']);
  489. $data = data::get('form');
  490. $this->assertEquals('davert', $data['user']);
  491. $this->module->see('Welcome to test app');
  492. $this->module->click('More info');
  493. $this->module->seeInCurrentUrl('/info');
  494. }
  495. /**
  496. * @issue https://github.com/Codeception/Codeception/issues/2408
  497. */
  498. public function testClickFailure()
  499. {
  500. $this->module->amOnPage('/info');
  501. $this->setExpectedException(
  502. 'Codeception\Exception\ElementNotFound',
  503. "'Sign In!' is invalid CSS and XPath selector and Link or Button element with 'name=Sign In!' was not found"
  504. );
  505. $this->module->click('Sign In!');
  506. }
  507. /**
  508. * @issue https://github.com/Codeception/Codeception/issues/2841
  509. */
  510. public function testSubmitFormDoesNotKeepGetParameters()
  511. {
  512. $this->module->amOnPage('/form/bug2841?stuff=other');
  513. $this->module->fillField('#texty', 'thingshjere');
  514. $this->module->click('#submit-registration');
  515. $this->assertEmpty(data::get('query'), 'Query string is not empty');
  516. }
  517. public function testClickLinkAndFillField()
  518. {
  519. $this->module->amOnPage('/info');
  520. $this->module->click('Sign in!');
  521. $this->module->seeCurrentUrlEquals('/login');
  522. $this->module->fillField('email', 'email@example.org');
  523. }
  524. public function testClickSelectsClickableElementFromMatches()
  525. {
  526. $this->module->amOnPage('/form/multiple_matches');
  527. $this->module->click('Press Me!');
  528. $this->module->seeCurrentUrlEquals('/info');
  529. }
  530. public function testClickSelectsClickableElementFromMatchesUsingCssLocator()
  531. {
  532. $this->module->amOnPage('/form/multiple_matches');
  533. $this->module->click(['css' => '.link']);
  534. $this->module->seeCurrentUrlEquals('/info');
  535. }
  536. /**
  537. * @expectedException PHPUnit_Framework_AssertionFailedError
  538. */
  539. public function testClickingOnButtonOutsideFormDoesNotCauseFatalError()
  540. {
  541. $this->module->amOnPage('/form/button-not-in-form');
  542. $this->module->click('The Button');
  543. }
  544. public function testSubmitFormWithoutEmptyOptionsInSelect()
  545. {
  546. $this->module->amOnPage('/form/bug3824');
  547. $this->module->submitForm('form', []);
  548. $this->module->dontSee('ERROR');
  549. }
  550. }