Skip to content

Commit 03d2df0

Browse files
committed
Initial commit
0 parents  commit 03d2df0

37 files changed

+3686
-0
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/.php_cs.cache
2+
/.php_cs
3+
/composer.phar
4+
/composer.lock
5+
/phpunit.xml
6+
/vendor/

.php_cs.dist

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
$header = <<<'HEADER'
4+
This file is part of the Panthère project.
5+
6+
(c) Kévin Dunglas <[email protected]>
7+
8+
For the full copyright and license information, please view the LICENSE
9+
file that was distributed with this source code.
10+
HEADER;
11+
12+
$finder = PhpCsFixer\Finder::create()->in(__DIR__);
13+
14+
return PhpCsFixer\Config::create()
15+
->setRiskyAllowed(true)
16+
->setRules([
17+
'@Symfony' => true,
18+
'@Symfony:risky' => true,
19+
'array_syntax' => [
20+
'syntax' => 'short',
21+
],
22+
'braces' => [
23+
'allow_single_line_closure' => true,
24+
],
25+
'declare_strict_types' => true,
26+
'header_comment' => [
27+
'header' => $header,
28+
'location' => 'after_open',
29+
],
30+
'modernize_types_casting' => true,
31+
'native_function_invocation' => true,
32+
'no_extra_consecutive_blank_lines' => [
33+
'break',
34+
'continue',
35+
'curly_brace_block',
36+
'extra',
37+
'parenthesis_brace_block',
38+
'return',
39+
'square_brace_block',
40+
'throw',
41+
'use',
42+
],
43+
'no_useless_else' => true,
44+
'no_useless_return' => true,
45+
'ordered_imports' => true,
46+
'phpdoc_add_missing_param_annotation' => [
47+
'only_untyped' => true,
48+
],
49+
'phpdoc_order' => true,
50+
'psr4' => true,
51+
'semicolon_after_instruction' => true,
52+
'strict_comparison' => true,
53+
'strict_param' => true,
54+
'ternary_to_null_coalescing' => true,
55+
])
56+
->setFinder($finder)
57+
;

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT license
2+
3+
Copyright (c) 2018-present Kévin Dunglas
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is furnished
10+
to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# Panthère
2+
**A browser testing and web scrapping library for [PHP](https://php.net) and [Symfony](https://symfony.com)**
3+
4+
*Panthère* is a convenient standalone library to scrape websites and to run end to end tests **using real browsers**.
5+
6+
Because it leverages [the W3C's WebDriver protocol](https://www.w3.org/TR/webdriver/) to drive native web browsers such
7+
as Google Chrome and Firefox, Panthère is super powerful.
8+
9+
Because it implements the popular Symfony's [BrowserKit](https://symfony.com/doc/current/components/browser_kit.html) and
10+
[DomCrawler](https://symfony.com/doc/current/components/dom_crawler.html) APIs, Panthère is very easy to use, and contains
11+
all features you need to test your apps. It will sound familiar if you ever created [a functional test for a Symfony app](https://symfony.com/doc/current/testing.html#functional-tests):
12+
the API is exactly the same!
13+
Keep in mind that Panthère doesn't depend of Symfony, it's a standalone library.
14+
15+
Because Panthère automatically finds your local installation of Chrome and launches it (thanks to [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/)),
16+
you don't have anything to install on your computer: no Selenium server nor obscure driver.
17+
In test mode, Panthère automatically starts your application using [the PHP built-in web-server](http://php.net/manual/en/features.commandline.webserver.php).
18+
Focus on writing your tests or web-scrapping scenario, Panthère takes care of everything else.
19+
20+
## Install
21+
22+
Use [Composer](https://getcomposer.org/) to install Panthère in your project:
23+
24+
composer req dunglas/panthere
25+
26+
## Basic Usage
27+
28+
```php
29+
<?php
30+
31+
require __DIR__.'/vendor/autoload.php';
32+
33+
$client = new \Panthere\Client();
34+
$crawler = $client->request('GET', 'http://api-platform.com'); // Yes, this website is 100% in JavaScript
35+
36+
$link = $crawler->selectLink('Support')->link();
37+
$crawler = $client->click($link);
38+
39+
// Wait for an element to be rendered
40+
$client->getWebDriver()->wait()->until(
41+
\Facebook\WebDriver\WebDriverExpectedCondition::visibilityOfElementLocated(\Facebook\WebDriver\WebDriverBy::className('support'))
42+
);
43+
44+
echo $crawler->filter('.support')->text();
45+
$client->getWebDriver()->takeScreenshot('screen.png'); // Yeah, screenshot!
46+
47+
$client->stop();
48+
```
49+
50+
## Testing Usage
51+
52+
The `PanthereTestCase` class allows you to easily write E2E tests. It automatically starts your app using the built-in PHP
53+
web server and let you crawl it using Panthère.
54+
It extends [PHPUnit](https://phpunit.de/)'s `TestCase` and provide all testing tools you're used to.
55+
56+
```php
57+
<?php
58+
59+
use Panthere\PanthereTestCase;
60+
61+
class E2eTest extends PanthereTestCase
62+
{
63+
public function testMyApp()
64+
{
65+
$client = static::createPanthereClient(); // Your app is automatically started using the built-in web server
66+
$crawler = $client->request('GET', static::$baseUri.'/mypage'); // static::$baseUri contains the base URL
67+
68+
$this->assertContains('My Title', $crawler->filter('title')->text()); // You can use any PHPUnit assertion
69+
}
70+
}
71+
```
72+
73+
To run this test:
74+
75+
phpunit tests/E2eTest.php
76+
77+
### A Polymorph Feline
78+
79+
If you are testing a Symfony application, `PanthereTestCase` automatically extends the `WebTestCase` class. It means that
80+
you can easily create functional tests executing directly the kernel of your application and accessing all your existing
81+
services. Unlike the Panthère's client, the Symfony's testing client doesn't support JavaScript or taking screenshots, but
82+
it is super-fast!
83+
84+
Alternatively (and even for non-Symfony apps), Panthère can also leverage the [Goutte](https://github.com/FriendsOfPHP/Goutte)
85+
web scrapping library. Goutte is an intermediate between the Symfony's test client and the Panthère one: it sends real HTTP
86+
requests, is fast and can browse any webpage, not only the ones of the application under test.
87+
But, because it is entirely written in PHP, Goutte doesn't support JavaScript and other advanced features.
88+
89+
The fun part is that the 3 libraries implement the exact same API, so you can switch from one to another just by calling
90+
the appropriate factory method, and find the good trade off for every single test case (do I need JavaScript, do I need
91+
to authenticate to an external SSO server, do I want to access the kernel of the current request...).
92+
93+
```php
94+
<?php
95+
96+
use Panthere\PanthereTestCase;
97+
98+
class E2eTest extends PanthereTestCase
99+
{
100+
public function testMyApp()
101+
{
102+
$symfonyClient = static::createClient(); // A cute kitty: the Symfony's functional test too
103+
$goutteClient = static::createGoutteClient(); // An agile lynx: Goutte
104+
$panthereClient = static::createGoutteClient(); // A majestic Panther
105+
106+
// Both Goutte and Panthère benefits from the built-in HTTP server
107+
108+
// enjoy the same API for the 3 felines
109+
// $*client->request('GET', '...')
110+
111+
$kernel = static::createKernel(); // You can also access to the app's kernel
112+
113+
// ...
114+
}
115+
}
116+
```
117+
118+
## Features
119+
120+
Unlike testing and web scrapping libraries you're used to, Panthère:
121+
122+
* executes the JavaScript code contained in webpages
123+
* supports all everything that Chrome (or Firefox) implements
124+
* can take screenshots
125+
* can wait for the appearance of elements loaded asynchronously
126+
* lets you run your own JS code or XPath queries in the context of the loaded page
127+
* supports custom [Selenium server](https://www.seleniumhq.org) installations
128+
* supports remote browser testing services including [SauceLabs](https://saucelabs.com/) and [BrowserStack](https://www.browserstack.com/)
129+
130+
## Documentation
131+
132+
Because Panthère implements the API of popular, it already has an extensive documentation:
133+
134+
* For the `Client` class, read [the BrowserKit's documentation](https://symfony.com/doc/current/components/browser_kit.html)
135+
* For the `Crawler` class, read [the DomCrawler's documentation](https://symfony.com/doc/current/components/dom_crawler.html)
136+
* For Webdriver, read [the Facebook's PHP WebDriver documentation](https://github.com/facebook/php-webdriver)
137+
138+
## Limitations
139+
140+
The following features are not currently supported:
141+
142+
* Crawling XML documents (only HTML is supported)
143+
* Updating existing documents (browsers are mostly used to consume data, not to create webpages)
144+
* Setting form values using the multidimensional PHP array syntax
145+
* Methods returning an instance of `\DOMElement` (because this library uses `WebDriverElement` internally)
146+
* Selecting invalid choices in select
147+
148+
Pull Requests are welcome to fill the remaining gaps!
149+
150+
## Credits
151+
152+
Created by [Kévin Dunglas](https://dunglas.fr). Sponsored by [Les-Tilleuls.coop](https://les-tilleuls.coop).

bin/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# External Vendors
2+
3+
Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/downloads

composer.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "dunglas/panthere",
3+
"type": "library",
4+
"description": "A browser testing and web scrapping library for PHP and Symfony.",
5+
"keywords": ["scrapping", "E2E", "testing", "webdriver", "selenium", "symfony"],
6+
"homepage": "https://dunglas.fr",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Kévin Dunglas",
11+
"email": "[email protected]",
12+
"homepage": "https://dunglas.fr"
13+
}
14+
],
15+
"require": {
16+
"php": ">=7.2",
17+
"facebook/webdriver": "^1.5",
18+
"symfony/browser-kit": "^4.0",
19+
"symfony/process": "^4.0"
20+
},
21+
"autoload": {
22+
"psr-4": { "Panthere\\": "src/" }
23+
},
24+
"autoload-dev": {
25+
"psr-4": { "Panthere\\Tests\\": "tests/" }
26+
},
27+
"extra": {
28+
"branch-alias": {
29+
"dev-master": "1.0.x-dev"
30+
}
31+
},
32+
"config": {
33+
"sort-packages": true
34+
},
35+
"require-dev": {
36+
"fabpot/goutte": "^3.2",
37+
"phpunit/phpunit": "^7.0",
38+
"symfony/framework-bundle": "^4.0"
39+
}
40+
}

examples/basic.php

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Panthère project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
require __DIR__.'/../vendor/autoload.php';
14+
15+
use Facebook\WebDriver\WebDriverBy;
16+
use Facebook\WebDriver\WebDriverExpectedCondition;
17+
use Panthere\Client;
18+
19+
$client = new Client();
20+
$crawler = $client->request('GET', 'http://api-platform.com'); // Yes, this website is 100% in JavaScript
21+
22+
$link = $crawler->selectLink('Support')->link();
23+
$crawler = $client->click($link);
24+
25+
// Wait for an element
26+
$client->getWebDriver()->wait()->until(
27+
WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::className('support'))
28+
);
29+
30+
echo $crawler->filter('.support')->text();
31+
$client->getWebDriver()->takeScreenshot('screen.png');
32+
33+
$client->stop();

phpunit.xml.dist

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
4+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
6+
backupGlobals="false"
7+
bootstrap="vendor/autoload.php"
8+
colors="true"
9+
>
10+
<testsuites>
11+
<testsuite name="Project Test Suite">
12+
<directory>tests</directory>
13+
</testsuite>
14+
</testsuites>
15+
16+
<filter>
17+
<whitelist processUncoveredFilesFromWhitelist="true">
18+
<directory>.</directory>
19+
<exclude>
20+
<directory>tests</directory>
21+
<directory>vendor</directory>
22+
</exclude>
23+
</whitelist>
24+
</filter>
25+
</phpunit>

0 commit comments

Comments
 (0)