セレンのスケヌリング

Seleniumを䜿甚したテストが1぀だけあるずしたす。 䜕が䞍安定になりたすか スピヌドアップする方法は 次に、2぀のテストがあるこずを想像しおください。 今癟を想像しおください。 このような䞀連のテストをすばやく解決する方法は テストの数が増え続けるずどうなりたすか


この蚘事では、Simon Stewartが、1぀のテストから数癟のテストを䞊行しお実行するずいう、難しいスケヌリングの方法を説明したす。 この堎合に発生する問題、およびこれらの問題を解決するための実甚的な方法に぀いお知りたす。 Javaコヌドず、テストむンフラストラクチャの開発に関するいく぀かの考えがありたす。



この蚘事のプロトタむプは、モスクワのハむれンバグ2017でのSimon Stewartによるレポヌトです。 サむモンはWebDriverの䜜成者です。WebDriverは、ほが11幎前の技術です。 圌は玄9幎前にSeleniumプロゞェクトマネヌゞャヌになりたした。 Googleで、圌はむンフラストラクチャ䞊で毎日数䞇から数癟䞇のテストにSeleniumを拡匵するこずに埓事しおいたした。 それからFacebookに行きたした。 圌は珟圚、W3Cテストおよび調敎グルヌプの䞀郚であるW3CのWebDriver仕様を開発しおいたす。 WebDriverに基づいお、暙準が䜜成されおいるず蚀えたす。


蚘事の過皋で、簡単なテストを怜蚎し、それをどのようにスケヌリングできるかを瀺したいず思いたす。 たず、パヌ゜ナルラップトップで起動し、最終的にはクラりドで動䜜し、呚囲のすべおの人の想像力を驚かしたす。 䞊叞は、あごを傷぀けお「私は明らかにあなたに十分なお金を払っおいない」ず蚀うでしょう 。 このために゜フトりェアを䜜成したす。


たず、決定したしょう-なぜテストが必芁なのですか 私たちは緑たたは赀の色が奜きだからではなく、私たちの䜜品が奜きだからでもありたせん。 唯䞀の機胜は、゜フトりェアが意図したずおりに機胜するこずを確認するこずです。 ゚ンドツヌ゚ンドのテスト䟋セレンは、バランスの取れたテストダむ゚ットの䞀郚である必芁がありたす。 それら以倖に䜕も消費されなければ、それから良いものは䜕も生たれたせん。 ただし、これに぀いおは埌で説明したす。



IDEで開かれおいる簡単な䟋から始めたしょう。 これはlongAndWrong()ず呌ばれ、これがテストの方法です。 実行がロヌカルで実行されるように、新しいFirefoxDriverを䜜成しおいたす。 次に、 WebDriverWait明瀺的な埅機を䜜成したす。 その埌、 http://localhost:8080に移動し、電子メヌルアドレスずパスワヌドを入力し、「todoの䜜成」芁玠が䜜成されるたで埅っお、最埌にクリックしたす。 誰もがSeleniumのリリヌスを芋おきたしたが、この特定のコヌドに異垞はありたせん。


このテストはひどいです。 理由を芋おみたしょう。 たず第䞀に、圌は幞運のために働いおいたす。 ペヌゞを受け取った埌、入力が行われる芁玠が衚瀺されるたで埅機したせん。 したがっお、WebDriverがペヌゞのロヌド時間を正しく掚枬するこずが期埅されたす。 䞊蚘の䟋では、HTMLが衚瀺されるだけなので、このアプロヌチは機胜したすが、他の堎合には問題が発生したす。


さらに重芁なのは、コヌドの別のセクションで芋る狂気です driver.findElements(By.tagName("button")).stream()など。 フィルタリングが発生し、䜕も芋぀からない堎合は、䜕らかの理由でAssertionErrorがスロヌされたす。 これらすべおの操䜜がクリックされた埌にのみ、必芁なものがすべお揃っおいるこずが明らかになるためです。 それは非垞に䞍気味に芋えるこずに誰もが同意したすか




これは、緑色で匷調衚瀺されおいたす-それは䜕ですか この構造党䜓の問題は、その脆匱性です。 そしおこれだけではありたせん。 たずえば、過床に長いXPathを䜿甚しおいる人はいたすか Longは、1行より長いこずを意味したす。 壊れやすいロケヌタヌは、テストが䞍気味になる䞻な理由の1぀です。 Selenideレポヌトでこの問題の解決に぀いお倚くを孊ぶこずができたす。


状況を改善するにはいく぀かの方法がありたす。 たず、゜ヌスコヌドにアクセスし、それを線集する暩限がある堎合、テスト枈みのアプリケヌション自䜓を正しく曞き換えるこずができたす。 圌はい぀もそこにいるわけではありたせん。 英囜の開発チヌムが刑務所のチケットを介しおルヌマニアのテストチヌムず通信するこずもありたす。たた、アプリケヌションが「正垞に」動䜜する䞀方で、テストに合栌しないず蚀う人もいたす。 コヌドにアクセスできる堎合、意味のある識別子を芁玠に远加できたすクラス、特定の属性。



おそらく、単䞀のBy入力を受け入れるWebDriver.findElementようなものがありたす。 手動で継承できたす。 セレクタ*ですべおの芁玠を探し、ツリヌ内のすべおの子芁玠を探し、属性の倀でフィルタリングしたす。 ただし、これは非垞に非効率的です。 WebDriverたたはSelenium APIぞの各呌び出しは、リモヌトプロシヌゞャぞの呌び出しであり、䜕らかの方法でネットワヌクを介しお行われたす。



代わりに、これらはすべおJavaScriptで蚘述でき、たったく同じこずを行いたす。 スラむドを芋るず、はるかに耇雑で怖いように芋えたすが、本質的には、コンテキストを取り、尋ねたす-あなたは本圓にWebDriverですか 次に、ラップされたWebDriverそこから抜出できたす。 たた、JavaScriptを実行するため、さらにJavaScriptをJavascriptExecutorキャストしたす。 ささいなこずなど。 誰かが知らないかもしれたせんが、 executeScript()は芁玠やその他のさたざたなものを返すこずができたす。 ブラりザに移動し、そこで䜕らかの䜜業を行い、結果を返したす。結果はJava型に正しくキャストされたす。 画面に衚瀺されるものは実際に機胜したす。


䞀郚の開発者は、既に実装されおいるこずを知らずに、そのような機胜を倢芋おいたす。 既に芁玠怜玢゚ンゞンが組み蟌たれおいるJSフレヌムワヌクがあるずしたす。 ランダムIDの生成など、単玔なものであっおも:-)このメカニズムを再実装するこずはできたせん。 必芁な芁玠を芋぀けるためにフレヌムワヌク自䜓に気軜に尋ねおください これにより、生掻が倧幅に簡玠化されたす。


たた、SeleniumがJavaScriptを䜿甚する理由も远加したす。 どの読者がアプリケヌションでJavaScriptフレヌムワヌクを䜿甚しおいたすか JQuery、React、Angular、たたは自家補の悪倢 ここでそれらずの察話には、JavaScriptも䜿甚する必芁がありたす。 私が匕甚した䟋では、jQueryはク゚リの数を远跡したした。 システムで䜕が起こっおいるのかに぀いお、システムから明確な情報を取埗する他の方法はありたせん。 時にはそれが必芁です。 さらに、WebDriverはナヌザヌの動䜜をシミュレヌトしようずしたす。 ナヌザヌは䜕をしたすか 圌はブラりザをクリックし、印刷し、芁玠をクリックしたす。 WebDriverずSeleniumにはできないこずがありたす。 HTTPステヌタスコヌドたたはネットワヌクトラフィックを監芖する堎合は、プロキシが必芁になる堎合がありたす。 ペヌゞで䜕かが発生した堎合、最良のオプションはペヌゞ自䜓に尋ねるこずです。 たずえば、かなり頻繁に識別子がランダムに䜜成されたすが、それらは敎然ず䜿甚されたす。 そのため、垞にその可甚性を圓おにするこずはできたせん。 ペヌゞに移動しお、芁玠がどの識別子であるかを確認し、通垞のむンデックスで䜿甚できたす。 このメカニズム党䜓により、グレヌボックステストを実行できたす。 「ホワむトボックス」ずは、テスト䞭に内郚のシステムに完党にアクセスできる堎合です。「ブラックボックス」の状況では、システムはたるで宇宙からやっお来たかのように密閉されたす。 たた、「グレヌボックス」をテストするずきは、最初は内郚のすべおが耇雑であるこずに頭を぀かんでから、ここで䜕かを倉曎し始め、そこにハンドラヌを挿入したす。 誰かがそれをハックず芋なしたす。 ラリヌりォヌルは、ファヌストクラスの開発者には、むラむラ、,慢、怠lazずいう3぀の品質が必芁だず考えおいたす。 焊りはすべおが今起こるこずを必芁ずしたす。 これにより、プログラムは高速になりたす。 あなたが䜕床も䜕床も䜕かをするように求められた堎合、あなたはこれをしないでしょう、これのための車がありたす。 怠け者は、䞀床は䜕床も働く準備ができおいるので、二床ず仕事をするこずはありたせん。 そのため、説明したアプロヌチでは、ハックではなく怠が芋えたす。 私はそれがどのように機胜するかを理解しようずするこずができたす-たたは、これに぀いお誰かに尋ねるこずができたす。 私の人生は楜になりたす。 たあ、ar慢は散財したいだけです。


別のトピックは、むベントの予想です。 Selenium- Wait<?>はそのようなこずがありWait<?> 。



誰もが圌女に粟通しおいるこずを願っおいたす。 Seleniumには䜕かを埅぀方法が2぀ありたす。明瀺的ず暗黙的です。 暗黙のうちに、私たちは䜕らかの䞍思議な時間を、明瀺的に埅っおいたす-あなたが今芋おいるものを䜿甚したす。 Seleniumチヌムからのアドバむス暗黙の期埅を䜿甚せず、 Wait.until䜿甚しおWait.until ..なぜですか 問題は、原則ずしお、人々は埅機する時間を知らないため、暗黙の埅機時間を最倧1分に蚭定するこずです。 すべおが正垞である堎合-これは問題ではなく、すべおが正垞です。 ただし、テストが倱敗した堎合、停止するたでに1分かかりたす。 これにより、通垞5〜15分かかるテストの起動に数時間かかるこずがありたす。


明瀺的な埅機時間が暗黙的な埅機よりも短い堎合、本質的には暗黙的な埅機の結果のみを凊理したす。 これは非垞に玛らわしく、そのようなテストをサポヌトするこずは䞍可胜です。 しかし、それでもできたす。


はい、明瀺的な期埅は暗黙的な期埅ず非垞に奇劙に盞互䜜甚したす。 奇劙な-「予枬䞍可胜」を意味したせん。 実際、すべおが完党に予枬可胜です。 暗黙の埅機時間を蚭定するコマンドが実行された堎合、タむムアりトになるたで実行は終了したせん。 暗黙の埅機時間が10秒であり、明瀺的な埅機時間が15であるずしたす。10秒埌に倱敗したこずを報告する芁求を実行したす。 次に、明瀺的な埅機は10ず15を比范しお、15の方が倧きいず刀断し、再び10秒間新しい埅機を実行したす。 頭を悩たせおいるのに、15ず尋ねたら20秒埅぀のはなぜですか 暗黙の期埅がい぀開始されるかは必ずしも明確ではありたせん。 したがっお、すべおが予想どおりに行われたすが、この内郚メカニズムを知らない堎合、倖郚からこの動䜜は非垞に奇劙に芋え、あなたの人生は非垞に困難になりたす。 私のアドバむスは、暗黙の期埅をたったく䜿甚しないこずです。 明瀺的な期埅には特定の情報が含たれたす。 テストスむヌトはコヌドをチェックするだけでなく、コヌドに䞍慣れな人のためにシステムがどのように機胜するかを蚘述したす。 たずえば、明瀺的な期埅は、珟時点では、むンタヌネット接続が発生する必芁がある、AJAX呌び出しを行う必芁がある、䜕かを曎新する必芁がある、などです。テストを読んでいる人が尋ねるかもしれたせん。 圌女はこれをすべきですか なぜ圌女はこれをしおいるのですか これにより、異なるアプロヌチでは䞍可胜な察話を行うこずができたす。 䞀般に、明瀺的な期埅ず暗黙的な期埅は盞互に䜜甚し、必ずしも明癜な方法ではありたせんが、暗黙的な期埅は垞に優先事項のように芋えたす。


元のテストに戻りたす。



圌はどこでも寝おいたせん。 玠朎なアプロヌチは、ある時点で埅機する必芁がある堎合Thread.sleep()実行するだけです。 このアプロヌチの問題は、テストの埅機時間が長すぎるこずです。 これは必芁ありたせん。


代わりに、 Waitクラスを䜿甚しおWait 。 その利点は、ゞェネリックを䜿甚するため、入力でスロヌする型がuntil()スロヌさuntil()こずです。 たずえば、 Wait.until(d -> driver.findElement(...))ず曞くこずができたす。さらに芁玠をスロヌするuntil芁玠を芋぀けたす。そこから.isDisplayed()を呌び出すず非垞に䟿利です。


さらに、WebElementぞのリンクを保存するず䟿利な堎合がありたすが、これは䜕らかの理由で回避されたす。 私はかなり倚くのクラむアントずコミュニケヌションを取り、圌らのサむトにアクセスし、芁玠ずのやり取りごずに再び探しおいるこずに気付きたす。 したがっお、各アクション呌び出しに察しお、2぀のリモヌト呌び出しが行われたす。 倀を取埗しおキヌを送信する必芁があるずしたす。 この堎合、倚くの堎合、最初にdriver.switchTo().activeElement().clear() driver.switchTo().activeElement().sendKeys() 、次にdriver.switchTo().activeElement().sendKeys()たす。


しかし、芁玠は同じです。 倉曎できる唯䞀の状況は、DOMから完党に削陀たたは切断された堎合です。この堎合、 StaleElementReferenceExceptionたす。 皆さんはこの䟋倖に倢䞭ではないですか 䜕かがDOMを曎新し、アむテムがなくなったこずを報告したす。 これは、埅機を蚭定する機䌚が倱われおいるこずを意味したす。目的のアむテムが衚瀺されるたで埅機するこずはできたせん。



画面に衚瀺されるコヌドは、埅機時間が最小化されるため、最適な時間を実行したす。 このコヌドでテストを再開するず、結果はたったく倉わりたせん。


ただ読んでいたすか これたでのずころ、新しいこずは䜕も蚀っおいないこずを願っおいたす:-)


だから、グレヌボックステスト。



期埅をより効果的にする方法がありたす。 䞊蚘のスラむドのisJqueryDone()メ゜ッドを芋おください。 圌はアクティブな操䜜の蚘録を保持したす。 jQuery.activeが0になるず、他に䜕も起きおいないこずが明らかになりたす。


䞀方で、なぜこれらの統蚈を芋぀けるために垞にペヌゞをプルするのですか 結局、JSフレヌムワヌクには同様のメカニズムがありたす。 たずえば、AngularJSの堎合。 なぜこれだけに制限できないのですか おそらく、ラむブラリは単に適切なレベルではありたせん。


それでも、問題はアプリケヌションを䜿甚しお解決できたす。 AJAX呌び出しを行っおおり、それが完了したかどうかを知りたいずしたす。 時々、DOMは曎新されず、新しいコンテンツが単玔にそこにスロヌされたす。 テストを続行できるかどうかが䞍明確になりたす。 たぶんすべおが同じたたで、テストを続行できたすか このような状況では、アプリケヌションレベルで監芖する䟡倀がありたす。意味のある操䜜を行うずきは、倉数を䜜成する必芁があり、操䜜が完了した埌、その倀をリセットできたす。 その埌、この倉数をチェックするこずが可胜になりたす。正しい倀をずるず、さらにテストを安党に続行できるこずを意味したす。


最埌に、 Seleniumに支揎を求めるこずができたす。 廃止されたアむテムぞの参照は、圹に立぀情報になる堎合がありたす。 アクションの結果ずしお、DOM芁玠が曎新され、DOMから削陀されるこずが予想される堎合、このアクションが実行される前に芁玠ぞのリンクを取埗できたす。 その埌、 StaleElementReference䟋倖を埅機し、新しい芁玠を芋぀けお返すこずができたす。 Waitでいく぀かの皮類の䟋倖を無芖できるため、このコヌドは簡朔で敎然ずしおおり、䜿いやすいです。 セレンはいく぀かの仕事を匕き受けたす。 DOMの倉曎、぀たり䜕かが内郚で動いたこずを瀺すシグナルは、テストをより安定させる玠晎らしい機䌚です。


ペヌゞオブゞェクトに移りたしょう。 叀代ロヌマ神話からダヌス神に぀いお聞いたこずがありたすか



ダヌスは過去ず未来を芋る双頭の神です。 1月が圌にちなんで名付けられた理由です。


ペヌゞオブゞェクトは倚くの堎合、誀解の犠牲になりたす。 SeleniumConfのスピヌチでは、PageObjectsの自動生成、芁玠の自動配眮に関するプレれンテヌションがたくさんありたした。 これはできたせん 「フレヌムワヌクを曞く」ように芋えお、あなたは玠晎らしく、実際にはすべおがもっず悪くなるので、それは矎しく芋えるだけです。


元の定矩では、Page ObjectはJanusの顔の1぀です。 これらはナヌザヌ向けのサヌビスです。 テスト察象のアプリケヌションにログむンペヌゞがある堎合は、ログむンペヌゞにログむンしお、サブゞェクト゚リアの蚀語でこれらすべおを説明したす。 このようなテストをビゞネス分析、プロゞェクトオヌナヌ、䞡芪に提瀺するず、アプリケヌションが正垞に動䜜しおいるかどうかがすぐに明らかになりたす。 しかし、Page Objectには、コヌドずペヌゞ構造の深い知識を必芁ずするJanusの別の顔がありたす。 これは、DRY「繰り返さないでください」、抜象化に必芁です。 LoadableComponent簡単にするために、SeleniumはLoadableComponentクラスを提䟛したす。 Page Objectずいう名前は本質をあたり反映しおいないようです。なぜなら、ここでは小さなピヌスでシミュレヌトでき、意図的にサむズを小さくできるからです。



ここではペヌゞオブゞェクトを䜿甚したテストがありたす。 前のテストずたったく同じです。 Userオブゞェクトを䜜成し、ログむンペヌゞに移動しおdriverず最倧タむムアりトを枡し、 get()を実行しget() 。 これは、 LoadableComponentモデルに埓っお発生したす。 ログむンするず、メむンペヌゞに戻りたす。 このテストでペヌゞオブゞェクトテンプレヌトを実装する方法の利点は、ナビゲヌトできるこずです。 ログむンペヌゞがMainPageをスロヌしなくなった堎合、関数の眲名を倉曎し、signUpメ゜ッドから䜕か他のものを返したす。 そのようなテストは実行する必芁さえなく、単にコンパむルしたせん。


デモを芋おみたしょう



これは簡単なToDoリストです。 それは驚くべきこずではありたせんが、アむデアをよく瀺しおいたす。



SignupPage.signUp()関数のコヌドでは、芁玠の怜玢のみが発生したす。



すべおが抜象化され、この1぀の関数に配眮されたす。 ログむンペヌゞのコヌドが倉曎された堎合、この関数は修正が必芁な唯䞀の堎所です。 開発者がUIワヌクフロヌを倉曎したか、䞀郚の芁玠の名前を倉曎した堎合、すべおの倉曎はここにありたす。 別の方法は、䜕癟䞇ものテストを実行しお修正するこずです。


これで基本は終わりです。 サポヌトの実装が簡単なテストがありたす。 Seleniumを拡匵する1぀の方法は、適切に実行され、簡単に保守できるテストを蚘述するこずです。 か぀お私は、圌らの仕事はテストスむヌトをグリヌンにするこずだず蚀われたクラむアントず䌚いたした。 すべおのチェックを削陀するこずでこの問題を解決し、䟋倖が発生した堎合はテストを正垞に完了したした。 䞀般にテストは成功したしたが、䜕の意味もありたせんでした。


たた、䜿甚されるデヌタは非垞に重芁です。 アプリケヌションが完党にステヌトレスである可胜性はほずんどありたせん。 ほずんどの堎合、ナヌザヌ、氞続デヌタなどがありたす。 デヌタは、テストをスケヌリングするずきに発生する問題の1぀です。 テストを䞊列化する方法に関するいく぀かの掚奚事項がありたす。



たず、Javaのstaticおよびシングルトンのデザむンパタヌンは悪であり、避けるべきです。 その理由は、静的フィヌルドはすべおのスレッドに共通しおいるためです。 このフィヌルドを2぀のテストから同時に倉曎するず、結果は予枬できなくなりたす。 クラむアントず通信するずきに、静的倉数を䜿甚しおWebDriverぞのリンクを保存するこずがよくありたす。 テストが䞊行しお実行される堎合のテストのクレむゞヌな動䜜に぀いお䞍満がありたす。時には動䜜する堎合ず動䜜しない堎合がありたす。 これは「競合状態」ず呌ばれたす。


staticを避けようずしお、 ThreadLocal䜿甚に切り替えたす。 しかし、圌も悪です。 スレッドアフィニティに䟝存するようになりたす。 テストが同じスレッドで実行されるこずを垞に確信しおいる堎合、レベル間でデヌタを安党にテレポヌトできたす。 デヌタWebDriverむンスタンス、ナヌザヌ名をテレポヌトする必芁があるずいう事実は、すでに悪い兆候であり、コヌドのにおいです。 したがっお、それらは䞍十分な構造です。 それらは理解するのが難しく、維持するのが難しいです。 理解するのが難しいテストは、倧きなトラブルの原因です。 ある同僚は、テストをデバッグするには、曞くよりも2倍の知性が必芁だず蚀いたした。 テストを曞くためにすべおの胜力を䜿甚しなければならなかった堎合、デバッグ䞭に行き止たりに陥り、そこから抜け出すこずは非垞に苊痛になりたす。 Javaを含め、関数型プログラミングが教えおくれたこずの1぀理想的なコヌドは䞍倉でなければならず、ステヌトレスであっおはなりたせん。 䜕かを倉曎するために前の図のように Todoオブゞェクトを䜜成する堎合、新しいオブゞェクトを䜜成する必芁がありたす。 可倉状態で同時に動䜜する2぀のスレッドがある堎合、これはひどい結果に぀ながりたす。


このテストを想像しおください。ナヌザヌFredがexample.comにアクセスしおログむンするず、登録が成功する必芁がありたす。 テストが初めお成功したずき。 ただし、テストを再床実行するず、Fredずいう名前のナヌザヌが既に登録されおいるため、テストがクラッシュしたす。 䞍快ですね。 私はあなたがそのようなものに垞に䌚うず確信しおいたす。 適切なアプロヌチは、各テストでデヌタを特別に準備するこずです。



テスト実行の間に環境を再䜜成するため、この問題は䞀般的ではありたせん。 , CI/CD ( ). ( , ), , .


, . — «» .



- , , . . dev , , . . , ! , happy path.


dev . : - ( - - , todo), , . , , , , . , , . — . , «».


-.



, . , LDAP. . , — , LDAP, . , . , , , . , . , , . , — , . , , - . . , . , JUnit Assume . .


, . , , . , , , . , . , . , , , — « ?», — « , ». . , , . , .


, , , , . Page Objects , , Screenplay, . , .


Selenium?



WebDriver Wire. , JSON- URL. : Json Wire Protocol W3C Dialect. OSS Dialect. , ChromeDriver, FirefoxDriver, Selenium Server, PhantomJS. W3C — Wire, . . — , , — Actions. Selenium Grid FirefoxDriver, drag-n-drop. . .


Selenium: .



. Selenium Grid. :



Selenium . :



, . . , , , . : Firefox, Chrome , , macOS — Safari:



, ChromeDriver, GeckoDriver, , . : ? Docker . — -!


, ? , , . . Docker , , — .


, Selenium , . — Selenium Docker . , . , GeckoDriver ChromeDriver, Firefox GeckoDriver . . Selenium- , Docker-.



( Chrome, — Firefox).



.



, Docker , . , . , . , 8 , . , , — .



, , . , Docker . Zalenium . .



Docker, :



. , . しかし、ニュアンスがありたす。 Windows, , , Edge.



. . , Zalenium .



Zalenium --sauceLabsEnabled true , Sauce Labs . , , Sauce Labs , .



*Zalenium*** .



Zalenium , /dashboard . , . , . , , Windows macOS, Sauce Labs , BrowserStack - .


. , . , Selenium , , HTTP, . , , .


. — DDoS, , . , . , - . Selenium , . , , .



, — « ». , — ( «»), - ( «»). , , . , . , — - , — .


, : - .



, . , -. : Selenium-. Selenium — .


. . , . Buck Bazel , , . .


, . , , , — . , , . , , . JavaScript, . , ? , , , . . , .


広告の分。 おそらくご存知のように、䌚議を行っおいたす。 — Heisenbug 2018 Piter , 17-18 2018 -. , ( — ), . , , !


Source: https://habr.com/ru/post/J351584/


All Articles