PHPとSeleniumを使用してブラウザを制御します

イントロ



みなさんこんにちは! 今日は、PHPを使用してSeleniumを操作する方法について説明します。



ほとんどの場合、これは、Webインターフェイスまたはパーサー/クローラーの自動テストを作成するタスクに直面しているときに必要です。



ウィキペディアから
「SeleniumはWebブラウザの自動化ツールです。

ほとんどの場合、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パターンを使用しています。 このアプローチは非常に効果的ですが、異なる方法を知っている場合は、コメントで教えてください。



それだけです ご清聴ありがとうございました!



All Articles