イントロ
みなさんこんにちは! 今日は、PHPを使用してSeleniumを操作する方法について説明します。
ほとんどの場合、これは、Webインターフェイスまたはパーサー/クローラーの自動テストを作成するタスクに直面しているときに必要です。
ウィキペディアから
「SeleniumはWebブラウザの自動化ツールです。
ほとんどの場合、Webアプリケーションのテストに使用されますが、これはそうではありません
に限定されます。 特に、phantomjsブラウザー用のSelenium WebDriverの実装
ウェブグラバーとしてよく使用されます。」
ほとんどの場合、Webアプリケーションのテストに使用されますが、これはそうではありません
に限定されます。 特に、phantomjsブラウザー用のSelenium WebDriverの実装
ウェブグラバーとしてよく使用されます。」
次のニュアンスを考慮します。
さあ、行こう!
1. BehatとMinkの調理
Behatは、もともと行動テスト(BDD)用に作成されたphpフレームワークです。 これは、よく知られているCucumber製品の公式PHP実装であり、他のプログラミング言語でよく使用されます。
彼自身は、Seleniumの使用方法を知らず、ブラウザーを使用せずに機能テストを作成することを意図しています。
通常のパッケージとしてインストールします。
$ composer require "behat/behat"
behatがブラウザーで動作することを教えるには、そのMink拡張機能と、特定のベンダー(この場合はSelenium)で動作するためのブリッジが必要です。 ミンクのページでベンダーの完全なリストを見つけることができます 。 バージョンを指定すると、composer.jsonは次のようになります。
"require": { "behat/behat" : "^3.4", "behat/mink-extension" : "2.2", "behat/mink-selenium2-driver" : "^1.3" }
インストール後、テストの実行を担当するvendor / bin / behatファイルが表示されます。 vendor / bin / behat --versionがインストールされたバージョンを示した場合、インストールが成功する可能性が高いと思われます:)
最後のフェーズは構成です
プロジェクトルートにメインのbehat.yml設定ファイルを作成します
default: # «» autoload: '': '%paths.base%/src/Context' suites: # facebook_suite: # () , Gherkin language paths: - '%paths.base%/scenario/facebook' contexts: # «» . # API - Dossier\Context\FacebookContext: # FacebookContext base_url: 'https://www.facebook.com/' user: 'email@gmail.com' pass: 'password' vk_suite: paths: - '%paths.base%/scenario/vk' contexts: - Dossier\Context\VkContext: # - "@lookup" services: # lookup: 'Dossier\Context\AccessLimit\Lookup' extensions: # , behat Behat\MinkExtension: browser_name: 'chrome' default_session: 'selenium2' selenium2: # Selenium . IP ( localhost ) wd_host: 'http://172.17.0.1:4444/wd/hub' # browser: chrome
スクリプトファイルまたは(* .featureファイル)-疑似言語Gherkinで記述されたymlファイルには、実際には、特定のスイートの実行中にブラウザーが実行する一連のステップバイステップの指示が含まれています。 上記のリンクをクリックすると、構文の詳細を確認できます。
そのような各「命令」は、クラスアノテーションで指定された正規表現の助けを借りて、「コンテキスト」クラスのメソッドと順番に一致します。 Behat \ MinkExtension \ Context \ MinkContext
メソッド自体の名前は役割を果たしませんが、CamelCaseに似た命名規則に従うことをお勧めします。
デフォルトのGherkinコンストラクトが欠落している場合、アノテーションを正しく指定することでMinkContextの下位クラスの機能を拡張できます。 この役割は、「コンテキスト」クラスによって実行されます。
2.環境のインストールと構成
すでにSeleniumを使用したことがある人は、テストを開始すると、ブラウザーがマシン上で起動し、.featureファイルで指定されたステップを実行することを知っています。
DockerでのSeleniumの実行は少し複雑です。 まず、コンテナ内にXが必要になります。次に、コンテナ内で何が起こるかを確認する必要があります。
Seleniumのスタッフは既にすべての面倒を見てくれているので、コンテナを回収する必要はありません。 スタンドアロンサーバーを搭載したコンテナは、ポート5900ですぐに利用できます。このポートでは、VNCクライアントから(たとえば、これから)ノックできます。 コンテナ内では、Chromeが事前にインストールされた使いやすいFluxboxインターフェイスが表示されます。 私の場合、次のようになります。
成功するために、サイトの指示に従って、Dockerコンテナーを実行できます。
$ docker run -d -p 4444:4444 -p 5900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug:3.11.0-californium
重要なポイントは、共有/ dev / shmボリュームがない場合、chromeは十分なメモリを持たず、起動できないため、指定することを忘れないでください。
私の場合、 docker-composeが使用され 、YAMLファイルは次のようになります。
version: '2' services: selenium: image: selenium/standalone-chrome-debug:3.11.0 ports: - "4444:4444" - "5900:5900" volumes: - /dev/shm:/dev/shm network_mode: "host"
ホストマシンで有効になっているVPNを介してFacebookにアクセスするテストが必要なので、 network_modeを指定することが重要です 。
composeを使用してコンテナーを開始するには、次のコマンドを実行します。
$ docker-compose up
次に、VNCを介してlocalhost:5900に接続し、コンテナー内のブラウザーを開きます。 成功し、上記のスクリーンショットに似たものが表示された場合、このレベルに合格しています。
3.理論から実践へ。 自動化
以下の例では、指定された姓と名ですべてのFacebookユーザーを取得します。 スクリプトは次のようになります。
src /シナリオ/ facebook / facebook.feature
Feature: Facebook Parse In order parse fb @first-level Scenario: Find person in facebook Given I am on "https://facebook.com/" When I fill in "email" with "some@gmail.com" And I fill in "pass" with "somepass" # Then I press tricky facebook login button Then I should see "" # Then I am searching by input params Then I dump users
そしてそれに応じて、Contextクラス(コンストラクターと名前空間は省略されます)
src / Context / FacebookContext.php
class FacebookContext extends MainContext { /** * @Then /^I press tricky facebook login button$/ */ public function pressFacebookButton() { $this->getSession()->getPage()->find( 'css', 'input[data-testid="royal_login_button"]' )->click(); } /** * . , , . * @Then /^I dump users$/ */ public function dumpUsers() { $session = $this->getSession(); $users = $this->getSession()->getPage()->findAll( 'xpath', $session->getSelectorsHandler() ->selectorToXpath('css', 'div._4p2o') ); if (!$users) { throw new \InvalidArgumentException("The user with this name was not found"); } $collection = new UserCollection('facebook_suite'); foreach ($users as $user) { $img = $user->find('xpath', $session->getSelectorsHandler() ->selectorToXpath( 'xpath', $session->getSelectorsHandler()->selectorToXpath('css', 'img') )); $link = $user->find('xpath', $session->getSelectorsHandler() ->selectorToXpath( 'xpath', $session->getSelectorsHandler()->selectorToXpath('css','a._32mo') )); $outputInfo = new OutputUserInfo('facebook_suite'); $outputInfo->setName($link ? $link->getText(): '') ->addPublicLinks($link ? $link->getAttribute('href') : '') ->setPhoto($img ? $img->getAttribute('src') : ''); $collection->append($outputInfo); } $this->saveDump($collection); } /** * URL * @Then /^I am searching by input params$/ */ public function search() { if (!Registry::has('query')) { throw new \BadMethodCallException('No search query received'); } $criteria = Registry::get('query'); $this->getSession()->visit("https://www.facebook.com/search/people/?q=" . urldecode($criteria->getQuery())); } }
多くの場合、FacebookContext :: pressFacebookButtonなどのカスタムメソッドが必要です。これは、デフォルトでは、minkのすべてのセレクターが名前|値| id | alt |タイトルでのみ検索できるためです。
別の属性で選択する必要がある場合は、独自のメソッドを作成する必要があります。 Facebook Loginボタンにはid属性がありますが、独自のロジックに従って値を定期的に変更します。 したがって、data-testidに再バインドする必要がありましたが、現時点では静的なままです。
ここで、これらすべてを開始するには、指定したポートでSeleniumが実行され、リッスンしていることを確認する必要があります。
それから:
$ vendor/bin/behat
コンテナ内で、ブラウザインスタンスが起動し、指定された指示に移動する必要があります。
4.カスタマイズbehat。 拡張機能
Behatフレームワークには、 behat.ymlを介して組み込まれた優れた拡張メカニズムがあります。 多くのフレームワーククラスは、単純に継承したいという誘惑を誘うためにfinalとして宣言されていることに注意してください。
この拡張機能を使用すると、behat機能を補完したり、新しいコンソール引数とオプションを宣言したり、他の拡張機能の動作を変更したりすることができます。
Behat \ Testwork \ ServiceContainer \ Extensionインターフェイス(behat.ymlでも指定されています)および必要に応じて補助クラス。
新しい入力引数--search-by-fullnameで検索対象の人の名前を受け入れるようにbehatに教えて、後でこのデータをスイート内で使用できるようにします。
以下は、必要な操作を実行するコードです。
SearchExtension
use Behat\Behat\Gherkin\ServiceContainer\GherkinExtension; use Behat\Testwork\Cli\ServiceContainer\CliExtension; use Behat\Testwork\ServiceContainer\Extension; use Behat\Testwork\ServiceContainer\ExtensionManager; use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; class SearchExtension implements Extension { /** * Extensions */ public function process(ContainerBuilder $container) { } /** * behat.yml * @return string */ public function getConfigKey() { return 'search'; } /** * , * configure(). * * @param ExtensionManager $extensionManager */ public function initialize(ExtensionManager $extensionManager){} /** * * @param ArrayNodeDefinition $builder */ public function configure(ArrayNodeDefinition $builder){ } /** * * @param ContainerBuilder $container * @param array $config */ public function load(ContainerBuilder $container, array $config) { $definition = new Definition('Dossier\BehatSearch\SearchController', array( new Reference(GherkinExtension::MANAGER_ID) )); $definition->addTag(CliExtension::CONTROLLER_TAG, array('priority' => 1)); $container->setDefinition( CliExtension::CONTROLLER_TAG .' . search', $definition ); } }
SearchExntesion :: loadメソッドでは、SearchControllerサービスがスローされます 。これは、パラメーターを宣言し、それらを受け入れ/処理する役割を直接担います。
SearchController
use Behat\Testwork\Cli\Controller; use Dossier\Registry; use Dossier\User\Criteria\FullnameCriteria; use Symfony\Component\Console\Command\Command as SymfonyCommand; use Symfony\Component\Console\Exception\InvalidOptionException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class SearchController implements Controller { const SEARCH_BY_FULLNAME = 'search-by-fullname'; /** * Configures command to be executable by the controller. * @param SymfonyCommand $command */ public function configure(SymfonyCommand $command) { $command->addOption( '--' . self::SEARCH_BY_FULLNAME, null, InputOption::VALUE_OPTIONAL, "Specify the search query based on fullname of the user. Must be started from surname" ); } /** * Executes controller. * * @param InputInterface $input * @param OutputInterface $output * * @return null|integer */ public function execute(InputInterface $input, OutputInterface $output) { $reflect = new \ReflectionClass(__CLASS__); foreach ($reflect->getConstants() as $constName => $option) { if ($input->hasOption($option) && ($optValue = $input->getOption($option))) { $queryArgs = explode(',', $optValue); Registry::set('query', new FullnameCriteria( $queryArgs[0], $queryArgs[1] ?? null, $queryArgs[2] ?? null) ); return null; } } throw new \InvalidOptionException("You must specify one of the following options to proceed: " . implode(', ', $reflect->getConstants())); } }
すべてが正しく宣言されている場合、使用可能なbehatコマンドのリストに新しい引数--search-by-fullnameが追加されます。
$ vendor/bin/behat --help
[mkardakov@mkardakov-local dossier.io]$ vendor/bin/behat --help | grep search-by-fullname --search-by-fullname[=SEARCH-BY-FULLNAME] Specify the search query based on fullname of the user. Must be started from surname [mkardakov@mkardakov-local dossier.io]$
SearchController内で入力を受け取ったら、それらをContextクラスに直接渡すか、データベースなどに保存できます。上記の例では、このためにRegistryパターンを使用しています。 このアプローチは非常に効果的ですが、異なる方法を知っている場合は、コメントで教えてください。
それだけです ご清聴ありがとうございました!