{"id":490288,"date":"2026-08-09T09:11:43","date_gmt":"2026-08-09T09:11:43","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=490288"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=490288","title":{"rendered":"Playwright: \u043f\u0438\u0448\u0435\u043c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Kotlin \u0438 Java"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u043a\u0430\u043a \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Playwright. \u0418 \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0441\u0435 \u043d\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435.<\/p>\n<p>\u041c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c PaaS-\u043e\u0431\u043b\u0430\u043a\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0430 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 Amvera. \u0418 \u043a\u043e\u0433\u0434\u0430 \u043d\u0430\u0448 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u0441\u0442\u0430\u043b \u0441\u043b\u043e\u0436\u043d\u044b\u043c, \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u0442\u0435\u043c, \u0447\u0442\u043e \u0440\u0435\u043b\u0438\u0437\u044b \u0441\u0442\u0430\u043b\u0438 \u0437\u0430\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0434 \u0431\u0430\u0433\u0438. \u0423 \u043d\u0430\u0441 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0443 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 VPS, \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0431\u043e\u0440\u043a\u0443, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 NGINX, \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0441 SSL \u0438 \u043c\u043d\u043e\u0433\u043e \u0447\u0442\u043e \u0435\u0449\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0434\u0443\u043c\u0430\u043b \u043e\u0431 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438. \u0410 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0431\u043e\u0442 \u0438\u043b\u0438 \u0441\u0430\u0439\u0442. \u0418 \u0432\u043e\u0442 \u044d\u0442\u043e \u201c\u043c\u043d\u043e\u0433\u043e \u0447\u0442\u043e \u0435\u0449\u0435\u201d \u0434\u043e\u043b\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0438 \u043d\u0435 \u043b\u043e\u043c\u0430\u0442\u044c\u0441\u044f. \u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u201c\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u0445\u201d \u0438 \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438\u0439 \u0444\u0430\u043a\u0442\u043e\u0440, \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u044b\u0435 \u0447\u0430\u0441\u0442\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Playwright.\u00a0<\/p>\n<p>\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 Playwright \u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u0432 \u043d\u0430\u0448\u0435\u043c \u0436\u0435 \u043e\u0431\u043b\u0430\u043a\u0435 Amvera, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b. \u041e\u043d\u0438 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u0434\u0430\u044e\u0442 \u0441\u0438\u0433\u043d\u0430\u043b, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a. \u0422\u0435\u043f\u0435\u0440\u044c, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0438 \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u043c\u044b \u043e\u0431 \u044d\u0442\u043e\u043c \u0431\u044b\u0441\u0442\u0440\u043e \u0443\u0437\u043d\u0430\u0435\u043c \u0438 \u0447\u0438\u043d\u0438\u043c.\u00a0 \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e, \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Playwright, \u0447\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.<\/p>\n<p>\u041c\u044b \u0432\u044b\u0431\u0440\u0430\u043b\u0438 Kotlin \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 Playwright \u0432 \u0440\u043e\u043b\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u0432 \u0431\u0435\u0437\u0433\u043e\u043b\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435. \u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e &#8212; \u044d\u0442\u043e \u044f\u0437\u044b\u043a, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0438 Java-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u0443\u0442\u0438 \u0438 \u043d\u0430\u0447\u043d\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Playwright.<\/p>\n<h2>\u041a\u0430\u043a \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c Playwright<\/h2>\n<p>\u0414\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 \u043d\u0430\u0448\u0438\u0445 \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044f \u0441\u043e\u0437\u0434\u0430\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 FlowExecutorService<strong>.<\/strong> \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 Playwright \u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b, \u0447\u0430\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0412 \u0435\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:<\/p>\n<pre><code>@Serviceclass FlowExecutorService(   private val context: ApplicationContext,   @param:Value(\"\\${playwright.headed}\") private val headed: Boolean,   @param:Value(\"\\${amvera.test-project-name}\") internal val amveraTestProjectName: String,   @param:Value(\"\\${amvera.username}\") internal val amveraUsername: String,   @param:Value(\"\\${amvera.password}\") internal val amveraPassword: String,   @param:Value(\"\\${amvera.host}\") internal val amveraHost: String,   @param:Value(\"\\${amvera.isAmvera}\") private val isAmvera: Boolean)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a \u043d\u0438\u043c \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u0440\u0435\u0434\u044b (\u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043e\u0442 \u043b\u0438\u0446\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c) \u0438 \u0445\u043e\u0441\u0442 \u041b\u041a (\u0442\u0435\u0441\u0442 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430 \u043f\u0440\u043e\u0434\u043e\u0432\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0442\u0430\u043a \u0438 \u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0441\u0442\u0435\u0439\u0434\u0436\u0430\u0445)\u00a0 \u043a\u0430\u043a \u0432 \u0431\u0435\u0437\u0433\u043e\u043b\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u0442\u0430\u043a \u0438 \u0441 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c \u043e\u043a\u043d\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430.<\/p>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043c\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 PlayWright \u0438 \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0425\u0440\u043e\u043c:<\/p>\n<pre><code>\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)\/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430. by lazy \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435.private val playwright by lazy { Playwright.create() }\/\/ \u041e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.\/\/ \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 100-200 \u041c\u0411 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0444\u043b\u043e\u0443.private val browser by lazy {   playwright.chromium().launch(       com.microsoft.playwright.BrowserType.LaunchOptions()           .setHeadless(!headed)           .setArgs(listOf(\"--start-maximized\"))   )}val activeTasks = ConcurrentHashMap&lt;String, String&gt;()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u043a\u043d\u043e \u043d\u0430 \u0432\u0435\u0441\u044c \u044d\u043a\u0440\u0430\u043d. \u0422\u0430\u043a \u0436\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u0445 Gradle:<\/p>\n<pre><code>dependencies {   implementation(\"com.microsoft.playwright:playwright:1.56.0\")}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u043e \u0437\u0430\u043a\u0430\u0447\u043a\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u0442\u0430\u043c \u0436\u0435, \u0432 build.gradle.kts<\/p>\n<pre><code>\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432tasks.register(\"installPlaywright\") {   group = \"verification\"   description = \"Downloads Playwright browsers into the project directory\"   doLast {       project.javaexec {\/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c classpath \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0433\u0434\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c playwright.jar           classpath = sourceSets[\"main\"].runtimeClasspath           mainClass.set(\"com.microsoft.playwright.CLI\")           args = listOf(\"install\")\/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 build\/libs\/\/ \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c layout.buildDirectory, \u0447\u0442\u043e\u0431\u044b \u043f\u0443\u0442\u044c \u0431\u044b\u043b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u041e\u0421           val browsersPath = File(project.layout.buildDirectory.asFile.get(), \"libs\/pw-browsers\")           environment(\"PLAYWRIGHT_BROWSERS_PATH\", browsersPath.absolutePath)        }   }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f graceful shutdown. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u0432 application.yml \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e:<\/p>\n<pre><code>server: #  \u0412\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0433\u0440\u0430\u0446\u0438\u043e\u0437\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 shutdown: graceful<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0418 \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u043d\u0430\u0448\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 FlowExecutorService \u043c\u0435\u0442\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 @PreDestroy<\/p>\n<pre><code>@PreDestroyfun shutdownService() {   log.info(\"\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u0430: \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0445 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u0440\u0443\u0442\u0438\u043d...\")   browser.close()   playwright.close()   serviceScope.cancel()<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u041a\u0430\u043a \u0432 Playwright \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0442\u0435\u043a\u0430\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0444\u043b\u043e\u0443?<\/h2>\n<p>\u0421\u0430\u043c\u044b\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430 \u0442\u0435\u0441\u0442\u0430 \u0432 \u043b\u043e\u0433\u0438. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b \u0441 \u0438\u043c\u0435\u043d\u0435\u043c, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u043c \u0434\u0430\u0442\u0443-\u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0444\u043b\u043e\u0443. \u042d\u0442\u0438 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0438 \u0438\u0445 \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u044d\u0442\u043e\u0439 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430 <strong>\u201c\/\u201d<\/strong><\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u043f\u0430\u043f\u043a\u0430 \u0441 \u043b\u043e\u0433\u0430\u043c\u0438 \u043d\u0435 \u0440\u0430\u0437\u0440\u0430\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0431\u0435\u0437\u0433\u0440\u0430\u043d\u0438\u0447\u043d\u043e \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c, \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0441\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 (\u0441\u0442\u0430\u0440\u0448\u0435 30 \u0434\u043d\u0435\u0439 \u043e\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0430\u0442\u044b):<\/p>\n<pre><code>private fun cleanOldLogs() {   \/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u0440\u0435\u0434\u0443 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f   val isAmvera = System.getenv(\"IS_AMVERA\")?.toBoolean() ?: true   val logDirPath = System.getenv(\"LOG_PATH\")?: \"test_results\"   log.warn(\"isAmvera $isAmvera\")   log.warn(\"logDirPath $logDirPath\")   val logDir = File(logDirPath)   if (!logDir.exists()) return   val monthAgo = Instant.now().minus(30, ChronoUnit.DAYS)   var deletedCount = 0   logDir.listFiles()?.forEach { file -&gt;       if (file.isFile &amp;&amp; Instant.ofEpochMilli(file.lastModified()).isBefore(monthAgo)) {           if (file.delete()) {               log.info(\"\u0423\u0434\u0430\u043b\u0435\u043d \u0441\u0442\u0430\u0440\u044b\u0439 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b: ${file.name}\")               deletedCount++           }       }   }   if (deletedCount &gt; 0) {       log.info(\"\u041e\u0447\u0438\u0441\u0442\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0423\u0434\u0430\u043b\u0435\u043d\u043e \u0444\u0430\u0439\u043b\u043e\u0432: $deletedCount\")   } else {       log.info(\"\u0421\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.\")   }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0437\u0430\u0433\u043b\u0430\u0432\u043d\u043e\u043c \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442\u0435 \u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u043e\u0434 \u043d\u0438\u043c\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043b\u043e\u0433\u043e\u0432 &#8212; \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u044d\u0442\u0438\u0445 \u0444\u043b\u043e\u0443 \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u043c \u0441 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u043c \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f &#8212; \u201c\u0423\u0441\u043f\u0435\u0448\u043d\u043e\u201d, \u201c\u0421 \u043e\u0448\u0438\u0431\u043a\u043e\u0439\u201d \u0438\u043b\u0438 \u201c\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435\u201d \u2013 \u0434\u043b\u044f \u0441\u0432\u0435\u0436\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0444\u043b\u043e\u0443. \u0421\u0442\u0430\u0442\u0443\u0441\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0441\u0435\u0441\u0441\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442, \u043d\u043e \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0438 \u0438\u0445 \u0432\u0438\u0434\u0435\u0442\u044c \u0432 \u044d\u0442\u043e\u043c \u0441\u043f\u0438\u0441\u043a\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u0443\u0436\u0435 \u043d\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442.<\/p>\n<p>\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u043d\u0430\u043c\u0438 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b\u043e\u0432 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e logback \u0447\u0435\u0440\u0435\u0437 logback-spring.xml \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0430\u043f\u043f\u0435\u043d\u0434\u0435\u0440\u0430 SIFT \u0434\u043b\u044f \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c FLOW_FILE_LOGGER (\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u043b\u043e\u0433\u0433\u0435\u0440 \u044f \u0431\u0443\u0434\u0443 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u043a\u043b\u0430\u0441\u0441\u0430\u0445 \u0441 \u0434\u0435\u043a\u043b\u0430\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0441\u0430\u043c\u043e\u0433\u043e \u0444\u043b\u043e\u0443. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043b\u043e\u0433\u0438 \u043d\u0435 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u044e\u0442 \u0444\u0430\u0439\u043b\u043e\u0432, \u0438\u0434\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0432 STDOUT):<\/p>\n<pre><code>&lt;configuration&gt;   &lt;appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\"&gt;       &lt;encoder&gt;           &lt;pattern&gt;%d{HH:mm:ss} %highlight(%-4level) %-50msg - %-15logger{15}%n&lt;\/pattern&gt;       &lt;\/encoder&gt;   &lt;\/appender&gt;   &lt;appender name=\"SIFT\" class=\"ch.qos.logback.classic.sift.SiftingAppender\"&gt;       &lt;discriminator&gt;           &lt;key&gt;logFileName&lt;\/key&gt;           &lt;defaultValue&gt;application&lt;\/defaultValue&gt;       &lt;\/discriminator&gt;       &lt;sift&gt;           &lt;appender name=\"FILE-${logFileName}\" class=\"ch.qos.logback.core.FileAppender\"&gt;               &lt;file&gt;${LOG_PATH:-test_results}\/${logFileName}.log&lt;\/file&gt;               &lt;append&gt;true&lt;\/append&gt;               &lt;encoder&gt;                   &lt;pattern&gt;%d{HH:mm:ss} %-4level %-50msg%n&lt;\/pattern&gt;               &lt;\/encoder&gt;           &lt;\/appender&gt;       &lt;\/sift&gt;   &lt;\/appender&gt;   &lt;root level=\"INFO\"&gt;       &lt;appender-ref ref=\"STDOUT\" \/&gt;   &lt;\/root&gt;   &lt;logger name=\"FLOW_FILE_LOGGER\" level=\"INFO\" additivity=\"false\"&gt;       &lt;appender-ref ref=\"STDOUT\" \/&gt;       &lt;appender-ref ref=\"SIFT\" \/&gt;   &lt;\/logger&gt;&lt;\/configuration&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Playwright<\/h2>\n<p>\u0414\u043b\u044f Playwright \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u0430, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043b\u043e\u0433\u0438\u043d\u0430 \u0438 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u201c\u0412\u043e\u0439\u0442\u0438\u201d. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435 FlowExecutorService \u043c\u0435\u0442\u043e\u0434 pushLogin<strong>:<\/strong><\/p>\n<pre><code>internal suspend fun pushLogin(page: Page) {   \/\/ \u041d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e \u0441 https:\/\/cloud.amvera.ru   \/\/ Playwright \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u0442 \u043d\u0430 https:\/\/id.amvera.ru\/   log.warn(\"\u0422\u0415\u0421\u0422\u0418\u0420\u0423\u042e $amveraHost\")   page.navigate(amveraHost)   \/\/ \u0412\u0432\u043e\u0434\u0438\u043c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c   log.info(\"\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043b\u043e\u0433\u0438\u043d: $amveraUsername\")   page.fill(\"#username\", amveraUsername)   page.fill(\"#password\", amveraPassword)   \/\/ \u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \"\u0412\u043e\u0439\u0442\u0438\"   page.click(\"#kc-login\")   \/\/ \u0416\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u043d\u0430 \u0434\u0430\u0448\u0431\u043e\u0440\u0434   \/\/ \u0416\u0434\u0435\u043c, \u043f\u043e\u043a\u0430 URL \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0431\u044b\u0442\u044c id.amvera.ru \u0438 \u0432\u0435\u0440\u043d\u0435\u0442\u0441\u044f \u043d\u0430 cloud.amvera.ru   page.waitForURL(\"${amveraHost}\/projects\")   log.info(\"\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u043b\u0438\u0441\u044c. \u0422\u0435\u043a\u0443\u0449\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430: ${page.title()}\")   takeScreenshot(page, \"login_success.png\")   log.info(\"\u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u043b\u043e\u0433\u0438\u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d \u043a\u0430\u043a login.png\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043c\u0435\u0442\u043e\u0434 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0442\u0438\u043f\u0430 Page, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 executeFlow() \u043d\u0430\u0448\u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 FlowExecutorService \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code>\/*** \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f* *\/fun executeFlow(flow: IFlow): Job {   \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043f\u043e\u0442\u043e\u043a\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0444\u043b\u043e\u0443   return serviceScope.launch {       val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern(\"MM-dd-HH-mm-ss\"))       val fileName = \"${timestamp} ${flow.name}.log\"       activeTasks[fileName] = \"Init\"       val contextOptions = Browser.NewContextOptions().apply {           if (headed) {               \/\/ \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0441 \u043e\u043a\u043d\u043e\u043c \u2014 \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443 \u0441\u0430\u043c\u043e\u043c\u0443 \u0440\u0435\u0448\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 (\u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u0442 maximized)               setViewportSize(null)           } else {               \/\/ \u0412 \u0444\u043e\u043d\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u2014 \u0436\u0435\u0441\u0442\u043a\u043e \u0437\u0430\u0434\u0430\u0435\u043c Full HD, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u0441\u0442\u043a\u0430 \u043d\u0435 \u043f\u043e\u0435\u0445\u0430\u043b\u0430               setViewportSize(1920, 1080)           }       }       val context = browser.newContext(contextOptions)       \/\/ \u041d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u0437\u0430\u043f\u0438\u0441\u044c \u0442\u0440\u0435\u0439\u0441\u0430       context.tracing().start(Tracing.StartOptions()           .setScreenshots(true)           .setSnapshots(true)           .setSources(true))       val page: Page = context.newPage()       \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 (click, fill, \u0438 \u0442.\u0434.)       page.setDefaultTimeout(MAX_WAIT_TIME_MS)       \/\/ \u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u0434\u043b\u044f \u0432\u0441\u0435\u0445 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u0439 (waitForSelector \u0438 \u0442.\u0434.)       page.setDefaultNavigationTimeout(MAX_WAIT_TIME_MS)       try {           withContext(MDCContext(mapOf(\"logFileName\" to fileName.removeSuffix(\".log\")))) {               log.info(\"--- \u0421\u0422\u0410\u0420\u0422 \u0424\u041b\u041e\u0423 \u0432 \u0444\u043e\u043d\u0435: ${flow.name} ---\")               activeTasks[fileName] = \"Running\"               flow.execute(this@FlowExecutorService, page)               activeTasks[fileName] = \"Success\"               log.info(\"--- \u0424\u041b\u041e\u0423 \u0417\u0410\u0412\u0415\u0420\u0428\u0415\u041d \u0423\u0421\u041f\u0415\u0428\u041d\u041e ---\")           }       } catch (e: Throwable) {           withContext(MDCContext(mapOf(\"logFileName\" to fileName.removeSuffix(\".log\")))) {                    activeTasks[fileName] = \"Error\"               log.error(\"\u041a\u0440\u0438\u0442\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 ${e.message}\")               log.error(\"\u0414\u0435\u043b\u0430\u044e \u0441\u043a\u0440\u0438\u043d fatalerror.jpg\")               takeScreenshot(page,\"fatalerror-${flow.name}.jpg\")           }       } finally {           page.close()           context.close()       }   }}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>IFlow \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u043a\u043b\u0430\u0441\u0441\u044b flow, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434\u043e\u043c executeFlow:<\/p>\n<pre><code>interface IFlow {   val name: String   suspend fun execute(runner: FlowExecutorService, page: Page)}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u043c \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438 \u0435\u0441\u0442\u044c \u043c\u0435\u0442\u043e\u0434 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443 execute<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 FlowExecutorService \u044f \u0432\u044b\u043d\u0435\u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u0435\u043b\u0430\u044e\u0449\u0438\u0435 \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u044b \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0432 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0435 \u0431\u0435\u0437\u043e\u0431\u0440\u0430\u0437\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0434\u0430\u0436\u0435 \u0432 \u0431\u0435\u0437\u0433\u043e\u043b\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435 \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u043f\u043e\u043d\u0438\u043c\u0430\u0442\u044c \u0447\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 \u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u0430. \u0411\u0435\u0437 \u043d\u0438\u0445, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438\u0448\u043b\u043e\u0441\u044c \u0431\u044b \u0434\u043e\u043b\u0433\u043e \u043b\u043e\u043c\u0430\u0442\u044c \u0433\u043e\u043b\u043e\u0432\u0443 \u2013 \u043f\u043e\u0447\u0435\u043c\u0443 \u0442\u0435\u0441\u0442 \u043f\u0430\u0434\u0430\u0435\u0442 \u0432 \u0441\u0430\u043c\u044b\u0445 \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0445 \u043c\u0435\u0441\u0442\u0430\u0445 \u0433\u0434\u0435 \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u0431\u044b, \u043d\u0438\u0447\u0435\u0433\u043e \u043d\u0435 \u043c\u0435\u0448\u0430\u0435\u0442 \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u043e\u043c\u0443 \u043f\u0440\u043e\u0442\u0435\u043a\u0430\u043d\u0438\u044e \u0444\u043b\u043e\u0443. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043f\u0440\u0438\u0447\u0438\u043d\u043e\u0439 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u0448\u043a\u0438 \u0441 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f\u043c\u0438, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u0447\u0430\u0441\u0442\u044c \u043a\u043d\u043e\u043f\u043a\u0438, \u0447\u0442\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0431\u0438\u0432\u0430\u0442\u044c Playwright \u043f\u043e \u0442\u0430\u0439\u043c\u0430\u0443\u0442\u0430\u043c. \u0418\u043d\u043e\u0433\u0434\u0430 \u043e\u043d\u0438 \u043f\u043e\u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f, \u0438\u043d\u043e\u0433\u0434\u0430 \u043d\u0435\u0442, \u0438 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0441\u0438\u043b\u044c\u043d\u043e \u0432\u0441\u0435 \u0437\u0430\u043f\u0443\u0442\u0430\u0442\u044c.\u00a0<\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0430\u043c \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u0441\u043e\u0437\u0434\u0430\u044e\u0449\u0430\u044f \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0432\u0441\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b:<\/p>\n<pre><code>internal fun takeScreenshot(page: Page, filename: String) {   val screenshotPath = Paths.get(getScreenshotsDir(), filename)   \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044e 'screenshots', \u0435\u0441\u043b\u0438 \u043e\u043d\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442   Files.createDirectories(screenshotPath.parent)   \/\/ \u0414\u0435\u043b\u0430\u0435\u043c \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u043e\u043f\u0446\u0438\u044e setFullPage   val fullPage = true   page.screenshot(       Page.ScreenshotOptions()           .setPath(screenshotPath)           .setFullPage(fullPage) \/\/ \u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0432\u0441\u0435\u0439 \u0434\u043b\u0438\u043d\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b   )   log.info(\"\u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d (FullPage: $fullPage): ${screenshotPath.toAbsolutePath()}\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0432 FlowExecutorService \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043d\u0435\u0441\u0442\u0438 \u0447\u0430\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432\u043e \u0444\u043b\u043e\u0443 \u043c\u0435\u0442\u043e\u0434\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u043c\u0435\u043d\u044f \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0432\u0430\u0436\u043d\u044b\u043c \u043c\u0435\u0442\u043e\u0434, \u043e\u0436\u0438\u0434\u0430\u044e\u0449\u0438\u0439 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0442\u0430\u0442\u0443\u0441\u0430 \u0442\u0435\u043a\u0443\u0449\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432 \u0446\u0438\u043a\u043b\u0435 waitForStatus():<\/p>\n<pre><code>   \/**    * \u0412 \u0446\u0438\u043a\u043b\u0435 \u0436\u0434\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u044f\u0435\u043c \u043e\u0431 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f\u0445    * *\/   internal suspend fun waitForStatus(needStatus: ProjectStatus, page: Page) {       log.info(\"\u041e\u0436\u0438\u0434\u0430\u0435\u043c \u0441\u0442\u0430\u0442\u0443\u0441 ${needStatus.text}\")       val startTime = System.currentTimeMillis()       val timeoutLimit = MAX_WAIT_TIME_MS*10       page.waitForTimeout(5000.0) \/\/ \u0414\u0430\u0435\u043c 5 \u0441\u0435\u043a\u0443\u043d\u0434 \u043d\u0430 \u043d\u0430\u0447\u0430\u043b\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0438 \u0441\u043c\u0435\u043d\u0443 \u0441\u0442\u0430\u0442\u0443\u0441\u0430       while (System.currentTimeMillis() - startTime &lt; timeoutLimit) {           \/\/ \u0421\u0447\u0438\u0442\u044b\u0432\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441           val currentStatus = getCurrentStatus(page)           if (currentStatus == needStatus) {               log.info(\"\u2705 \u0423\u0441\u043f\u0435\u0445: \u0414\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u0441\u0442\u0430\u0442\u0443\u0441 '${needStatus.text}'\")               return           }           if (currentStatus == null) {               log.error(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u0447\u0438\u0442\u0430\u0442\u044c \u0441\u0442\u0430\u0442\u0443\u0441\")               delay(1000)\/\/                throw RuntimeException(\"\u0421\u0442\u0430\u0442\u0443\u0441 \u043d\u0435 \u0447\u0438\u0442\u0430\u0435\u0442\u0441\u044f\")           }           log.info(\"\u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441: ${currentStatus?.text}. \u0416\u0434\u0435\u043c...\")           \/\/ \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a, \u043f\u043e\u043a\u0430 \u0441\u043f\u0438\u043c           delay(POLL_INTERVAL_MS)       }       \/\/ \u041e\u0448\u0438\u0431\u043a\u0430 \u043a\u0438\u0434\u0430\u0435\u0442\u0441\u044f \u0422\u041e\u041b\u042c\u041a\u041e \u0435\u0441\u043b\u0438 \u0432\u044b\u0448\u043b\u0438 \u0438\u0437 \u0446\u0438\u043a\u043b\u0430 \u043f\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438       throw RuntimeException(\"\u0422\u0430\u0439\u043c\u0430\u0443\u0442: \u0421\u0442\u0430\u0442\u0443\u0441 '${needStatus.text}' \u043d\u0435 \u0434\u043e\u0441\u0442\u0438\u0433\u043d\u0443\u0442 \u0437\u0430 ${timeoutLimit\/1000} \u0441\u0435\u043a.\")   }<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<h2>\u041a\u043b\u0430\u0441\u0441 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0449\u0438\u0439 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430<\/h2>\n<p>\u0417\u0430\u0434\u0443\u043c\u043a\u0430 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u043b\u0430 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0438\u043c\u0435\u0442\u044c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043e\u043f\u044b\u0442\u0430. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u0441\u0432\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 \u0444\u043b\u043e\u0443, \u0438 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0442\u0430\u043a\u043e\u0433\u043e \u0444\u043b\u043e\u0443 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c\u0441\u044f \u0441\u0432\u043e\u0439 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b \u0441 \u0438\u043c\u0435\u043d\u0435\u043c \u044d\u0442\u043e\u0433\u043e \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f. \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438 \u0432 \u0422\u0417 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u0441\u043b\u043e\u0436\u043d\u044b\u0435, \u0442\u043e \u043f\u043e\u043a\u0430 \u043e\u043d\u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u044e\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e flow1, flow2 \u0438 \u0442.\u0434. \u0430 \u0447\u0442\u043e \u0438\u043c\u0435\u043d\u043d\u043e \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434 \u043a\u0430\u043f\u043e\u0442\u043e\u043c \u2013 \u043e\u043f\u0438\u0441\u0430\u043d\u043e \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u0433\u0443\u0433\u043b-\u0434\u043e\u043a \u0444\u0430\u0439\u043b\u0435.<\/p>\n<p>\u0412 \u043d\u0430\u0447\u0430\u043b\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 \u0444\u043b\u043e\u0443 \u0438\u0434\u0435\u0442 \u0430\u043d\u043e\u043d\u0441\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043b\u043e\u0433\u0433\u0435\u0440\u0430 FLOW_FILE_LOGGER<\/p>\n<pre><code>private val log = KotlinLogging.logger(\"FLOW_FILE_LOGGER\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041a\u0430\u043a \u043c\u044b \u043f\u043e\u043c\u043d\u0438\u043c, \u0438\u043c\u0435\u043d\u043d\u043e \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0441\u043e\u0437\u0434\u0430\u044e\u0442\u0441\u044f \u043b\u043e\u0433-\u0444\u0430\u0439\u043b\u044b \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u0435 \u0434\u043b\u044f \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u0438. \u0412\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043b\u043e\u0433\u0433\u0435\u0440\u044b \u043f\u0438\u0448\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0432 STDOUT<\/p>\n<p>\u0414\u0430\u043b\u044c\u0448\u0435 \u043c\u044b \u043e\u0431\u044c\u044f\u0432\u043b\u044f\u0435\u043c \u0441\u0430\u043c \u043a\u043b\u0430\u0441\u0441 \u0444\u043b\u043e\u0443 \u0438 \u0437\u0430\u0434\u0430\u0435\u043c \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u0447\u0430\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430 \u043b\u043e\u0433\u043e\u0432 \u043f\u043e \u044d\u0442\u043e\u043c\u0443 \u0444\u043b\u043e\u0443:<\/p>\n<pre><code>@Componentclass Flow1: IFlow {   override val name = \"Flow1\"   val projectName = \"Flow1\" \/\/\u043d\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0438\u0436\u043d\u0435\u0435 \u043f\u043e\u0434\u0447\u0435\u0440\u043a\u0438\u0432\u0430\u043d\u0438\u0435, \u0431\u0443\u0434\u0435\u0442 \u0441\u0442\u043e\u043f\u0438\u0442\u044c\u0441\u044f \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0432\u0432\u043e\u0434\u0430 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0430\u043a \u0436\u0435 \u0442\u0430\u043c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 \u0441\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b projectName &#8212; \u0438\u043c\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043d\u0430\u0434 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043d\u0430\u0448 \u0442\u0435\u0441\u0442. \u041d\u0443 \u0438 \u043a\u043e\u043d\u0435\u0447\u043d\u043e \u0436\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c \u043c\u0435\u0442\u043e\u0434 execute() \u043d\u0430\u0448\u0435\u0433\u043e \u0438\u043c\u043f\u043b\u0435\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 IFlow:<\/p>\n<pre><code>override suspend fun execute(runner: FlowExecutorService, page: Page) {   log.info(\"\u0412\u0432\u043e\u0434\u0438\u043c \u043a\u0440\u0435\u0434\u044b\")   runner.pushLogin(page)   log.info(\"\u0417\u0430\u043a\u0440\u043e\u0435\u043c \u0432\u0441\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c\")   delay(5000)   runner.closeAllNotifications(page)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0422\u0443\u0442 \u043c\u044b \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u0432\u0430\u0435\u043c\u0441\u044f \u043e\u0442 \u043b\u0438\u0446\u0430 Playwright \u0438 \u0437\u0430\u043a\u0440\u044b\u0432\u0430\u0435\u043c \u0432\u0441\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u0448\u043a\u0438 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043d\u0430\u043c \u043d\u0435 \u043c\u0435\u0448\u0430\u043b\u0438 \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c. \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 runner \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0442\u043e\u0442 \u0441\u0430\u043c\u044b\u0439 FlowExecutorService, \u043a\u0443\u0434\u0430 \u043c\u044b \u0432\u044b\u043d\u0435\u0441\u043b\u0438 \u0447\u0430\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0440\u0430\u0431\u043e\u0442\u044b:<\/p>\n<pre><code>log.info(\"\u0416\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439\")\/\/ \u0416\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043f\u0440\u043e\u0435\u043a\u0442\u0430\/\u0441\u0431\u043e\u0440\u043a\u0438page.waitForURL(\"${runner.amveraHost}\/projects\/applications\") \/\/ \u0416\u0434\u0435\u043c URLlog.info(\"--&gt;\u041f\u0435\u0440\u0435\u0448\u043b\u0438 \u0432 \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432\")log.info(\"\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043b\u0438 \u043f\u0440\u043e\u0435\u043a\u0442 \u0432 \u0441\u043f\u0438\u0441\u043a\u0435, \u0435\u0441\u043b\u0438 \u0434\u0430 - \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u0432 \u043d\u0435\u0433\u043e\")val projectLocator2 = runner.getProjectLocator(projectName, page)require(projectLocator2.count() &gt; 0) {   \"\u2139\ufe0f \u041f\u0440\u043e\u0435\u043a\u0442 '$projectName' \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043b\u0441\u044f.\"}projectLocator2.click()log.info(\"\u041f\u0440\u043e\u0435\u043a\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043d\u0430\u0439\u0434\u0435\u043d \u0438 \u043e\u0442\u043a\u0440\u044b\u0442\")\/\/\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0435\u043c \u0440\u0435\u0434\u043a\u0438\u0439 \u0441\u0442\u0430\u0442\u0443\u0441 \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043b\u043e\u0441\u044c \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044fval status = runner.getCurrentStatus(page)if(ProjectStatus.NOTDEPLOYED==status) {   delay(1000)   val status2 = runner.getCurrentStatus(page)   if(ProjectStatus.NOTDEPLOYED==status) {       log.warn(\"\u0421\u0442\u0430\u0442\u0443\u0441 \u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0441\u043b\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430!\")       log.warn(\"\u041f\u0440\u0438\u043d\u0443\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0435\u0440\u0435\u0441\u0431\u043e\u0440\u043a\u0443!\")       runner.pushBuildButton(page)   }}\/\/\u043f\u043e\u0434\u043e\u0436\u0434\u0435\u043c \u043f\u043e\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442 \u0441\u043e\u0431\u0435\u0440\u0435\u0442\u0441\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u0441\u044f, \u0437\u0430\u043c\u0435\u0440\u0438\u043c \u0432\u0440\u0435\u043c\u044fwaitSuccessWithMeasureTime(page, runner)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041c\u044b \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u043d\u0430\u0439\u0442\u0438 \u0435\u0433\u043e \u0432 \u043e\u0431\u0449\u0435\u043c \u0441\u043f\u0438\u0441\u043a\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0432 \u043b\u0438\u0447\u043d\u043e\u043c \u043a\u0430\u0431\u0438\u043d\u0435\u0442\u0435 \u0438 \u043f\u044b\u0442\u0430\u0435\u043c\u0441\u044f \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0438 \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u044c \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e waitSuccessWithMeasureTime.<\/p>\n<p>\u0417\u0430\u0442\u0435\u043c \u043c\u044b \u0438\u0434\u0435\u043c \u043f\u043e \u043d\u0430\u0448\u0435\u0439 \u0441\u0445\u0435\u043c\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f &#8212; \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0432 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u0444\u0430\u0439\u043b\u044b \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0441\u043c\u043e\u0442\u0440\u0438\u043c \u043a\u0430\u043a \u043e\u0442\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u043e\u0448\u0438\u0431\u043e\u043a, \u0447\u0442\u043e \u0432\u0441\u043f\u043b\u044b\u0432\u0430\u044e\u0442 \u0432\u0441\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438, \u0447\u0442\u043e \u043e\u043d\u0438 \u043f\u043e\u043f\u0430\u0434\u0430\u044e\u0442 \u0432 \u043b\u043e\u0433 \u0441\u0431\u043e\u0440\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<pre><code>\/\/\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439uploadAdditionalFileToRepository(page)\/\/\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c \u0444\u0430\u0439\u043b \u0432 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0435 \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435uploadAdditionalFileToWarehouse(page, runner)\/\/\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0447\u0442\u043e \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u043d\u044b\u0435 \u0444\u0430\u0439\u043b\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442\u0441\u044f \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u0438 \u043f\u043e\u0441\u0442. \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0435checkFilesArePresent(page)\/\/\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c \u043e\u0448\u0438\u0431\u043e\u0447\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438\/\/ \u0438 \u043e\u0448\u0438\u0431\u043a\u0443 \u043f\u0440\u0438 \u0441\u0431\u043e\u0440\u043a\u0435 \u043f\u0440\u0438 \u043b\u0438\u0448\u043d\u0438\u0445 \u043f\u043e\u043b\u044f\u0445 (\u0432 \u0442.\u0447. \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u0432 \u043b\u043e\u0433\u0435)uploadWrongConfigToRepositoryAndTestAlerts(page,runner)\/\/\u0442\u0435\u043f\u0435\u0440\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u043c \u0432\u0435\u0440\u043d\u0443\u044e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u0438 \u043f\u0435\u0440\u0435\u0441\u043e\u0431\u0435\u0440\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442, \u0443\u0431\u0435\u0434\u0438\u043c\u0441\u044f \u0447\u0442\u043e \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043b\u0441\u044fuploadGoodConfigToRepositoryAndRebuild(page,runner)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u043a\u043e\u043d\u0446\u0435 \u043c\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0437\u0430\u0432\u0435\u0434\u043e\u043c\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0410\u043c\u0432\u0435\u0440\u0430 \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u0441 \u043d\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u043e\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f.\u00a0<\/p>\n<p>\u041d\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043e\u043a \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0447\u0442\u043e \u043f\u0440\u043e\u0435\u043a\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0438 \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0441\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0435\u043a\u0442, \u0447\u0442\u043e\u0431\u044b \u043e\u043d \u043d\u0435 \u0440\u0430\u0441\u0445\u043e\u0434\u043e\u0432\u0430\u043b \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u0430:<\/p>\n<pre><code>\/\/ \u041f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442runner.pushRerunBtton(page)\/\/\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0440\u0430\u0431\u043e\u0442\u0443 \u0441\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430checkStaticAnalizator(page)\/\/\u041e\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043c \u043f\u0440\u043e\u0435\u043a\u0442runner.pushStopButton(page)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0412 \u0441\u0442\u0430\u0442\u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0435, \u043a\u0430\u043a \u044f \u0443\u0436\u0435 \u043f\u0438\u0441\u0430\u043b, \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043e \u0445\u0440\u0430\u043d\u0435\u043d\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0432 \u0442\u0435\u043b\u0435 \u0444\u0430\u0439\u043b\u0430 \u0441 \u043a\u043e\u0434\u043e\u043c (\u043c\u044b \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u043c \u0432\u044b\u043d\u043e\u0441\u0438\u0442\u044c \u0438\u0445 \u0432 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0441 \u0442\u0438\u043f\u043e\u043c \u0421\u0435\u043a\u0440\u0435\u0442 \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430), \u0430 \u0442\u0430\u043a\u0436\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u043e \u0442\u0438\u043f\u043e\u0432\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435 \u043c\u043d\u043e\u0433\u0438\u0445 \u043d\u0430\u0448\u0438\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 &#8212; \u043f\u0438\u0441\u0430\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u044f\u0435\u043c\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 \u0411\u0414, \u043c\u0438\u043c\u043e \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e\u0433\u043e \u0445\u0440\u0430\u043d\u0438\u043b\u0438\u0449\u0430.<\/p>\n<h2>\u0412\u0430\u0436\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043f\u0440\u0438 Playwright \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438<\/h2>\n<p>\u0421 \u043f\u043e\u043b\u043d\u044b\u043c \u043a\u043e\u0434\u043e\u043c \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0432 <strong>\u043d\u0430\u0448\u0435\u043c \u0433\u0438\u0442\u0445\u0430\u0431 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 <\/strong><a href=\"https:\/\/github.com\/amvera-academy\/playwright-autotests\"><strong>https:\/\/github.com\/amvera-academy\/playwright-autotests<\/strong><\/a>, \u043f\u043e\u043a\u0430\u0436\u0443 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u043a\u0430\u0437\u0430\u043b\u0438\u0441\u044c \u043c\u043d\u0435 \u0434\u043e\u0441\u0442\u043e\u0439\u043d\u044b\u043c\u0438 \u0432\u0430\u0448\u0435\u0433\u043e \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0442\u0430\u043a \u043c\u044b \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043f\u043e\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0432\u0430\u043b\u0438\u0434\u0438\u0440\u0443\u0435\u043c \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043d\u0430 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u043f\u0443\u0442\u0438:<\/p>\n<pre><code>fun fillAndCheckConfigStepInMaster(page: Page) {     log.info(\"\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c Runtime: Python\")   \/\/ \u043a\u043b\u0438\u043a\u0430\u0435\u043c \u043f\u043e Combobox \u0434\u043b\u044f \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0441\u043f\u0438\u0441\u043a\u0430   \/\/ \u041d\u0430\u0445\u043e\u0434\u0438\u043c Python \u0432 \u043f\u043e\u044f\u0432\u0438\u0432\u0448\u0435\u043c\u0441\u044f \u0441\u043f\u0438\u0441\u043a\u0435   val runtimeCombobox = page.getByText(\"\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0435\").locator(\"~ button[role=combobox]\")   runtimeCombobox.click()   val pythonOptionLocator = page.getByRole(AriaRole.OPTION, Page.GetByRoleOptions().setName(\"Python\"))   pythonOptionLocator.click()   \/\/\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0434\u0435\u0444\u043e\u043b\u0442\u043d\u044b\u0445 \u043f\u043e\u043b\u0435\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0430   checkDefaultConfigFieldsFillingInMaster(page)   \/\/\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u0435\u0439 \u043a\u043e\u043d\u0444\u0438\u0433\u0430 \u043d\u0430 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f   checkValidationConfigInMaster(page)   \/\/\u043f\u0440\u043e\u043f\u0438\u0448\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435 \u0447\u0442\u043e\u0431\u044b \u0432\u0441\u0435 \u0441\u043e\u0431\u0440\u0430\u043b\u043e\u0441\u044c   fillConfigInMaster(page)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0410 \u0442\u0430\u043a \u043c\u044b \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043d\u0430 \u0432\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0435:<\/p>\n<pre><code>fun checkValidationConfigInMaster(page: Page){   log.info(\"--&gt;\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u0435\u0439 \u0432 \u043c\u0430\u0441\u0442\u0435\u0440\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0437\u0430\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438\")   \/\/ \u041f\u0443\u0442\u044c \u043a \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u043c   val requirementsLocator = page.locator(\"input[name='build.requirementsPath']\")   val scriptNameLocator = page.locator(\"input[name='run.scriptName']\")   val actualRequirements = requirementsLocator.inputValue()   val wrongRequirements = \"missing-requirements.txt\"   val actualScriptName = scriptNameLocator.inputValue()   val wrongScriptName = \"missing_script.py\"   log.info(\"\u0412\u0432\u043e\u0434\u0438\u043c \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f requirements.txt: $wrongRequirements\")   requirementsLocator.fill(wrongRequirements)   \/\/ \u0418\u0449\u0435\u043c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0435. \u0412 HTML \u043e\u043d\u043e \u0438\u043c\u0435\u0435\u0442 id, \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e\u0449\u0438\u0439\u0441\u044f \u043d\u0430 -form-item-message   \/\/ \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u0439\u0442\u0438 \u0435\u0433\u043e \u043f\u043e \u0442\u0435\u043a\u0441\u0442\u0443, \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u043d \u0443\u043d\u0438\u043a\u0430\u043b\u0435\u043d \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0431\u043b\u043e\u043a\u0430   val requirementsError = page.locator(\"p:has-text('\u0412 \u043a\u043e\u0434\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0443\u0442\u044c. \u0421\u043e\u0437\u0434\u0430\u0442\u044c \u0444\u0430\u0439\u043b requirements.txt \u043c\u043e\u0436\u043d\u043e \u043d\u0430 \u044d\u0442\u0430\u043f\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 Upload files \u0438\u043b\u0438 \u043f\u043e \u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u0438 \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e requirements.txt')\")   scriptNameLocator.click()   \/\/ \u0416\u0434\u0435\u043c \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0448\u0438\u0431\u043a\u0438 (\u043d\u0430 \u0441\u043b\u0443\u0447\u0430\u0439 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438)   requirementsError.waitFor(Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE))   require(requirementsError.isVisible) {       \"\u041e\u0428\u0418\u0411\u041a\u0410: \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u043c requirements.txt \u043d\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c\"   }   log.info(\" \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0434\u043b\u044f requirementsPath \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430\")   requirementsLocator.fill(actualRequirements)   \/\/ \u0418\u043c\u044f \u0441\u043a\u0440\u0438\u043f\u0442\u0430   scriptNameLocator.fill(wrongScriptName)   log.info(\"\u0412\u0432\u043e\u0434\u0438\u043c \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u0441\u043a\u0440\u0438\u043f\u0442\u0430: $wrongScriptName\")   val scriptError = page.locator(\"p:has-text('\u0412 \u043a\u043e\u0434\u0435 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0444\u0430\u0439\u043b, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0443\u0442\u044c')\")       .filter(Locator.FilterOptions().setHasNotText(\"\u0418\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f \u043f\u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044e\")) \/\/ \u0418\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0435\u0440\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u043e\u043d\u043e \u0432\u0434\u0440\u0443\u0433 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u0442 \u043f\u043e \u0442\u0435\u043a\u0441\u0442\u0443   requirementsLocator.click()   scriptError.waitFor(Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE))   require(scriptError.isVisible) {       \"\u041e\u0428\u0418\u0411\u041a\u0410: \u0421\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u043c \u0438\u043c\u0435\u043d\u0438 \u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u043d\u0435 \u043f\u043e\u044f\u0432\u0438\u043b\u043e\u0441\u044c\"   }   log.info(\" \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u0438\u043c\u0435\u043d\u0438 \u0441\u043a\u0440\u0438\u043f\u0442\u0430 \u043f\u0440\u043e\u0439\u0434\u0435\u043d\u0430!\")   scriptNameLocator.fill(actualScriptName)<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u0435. \u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043c\u044b \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u0445\u0438\u0442\u0440\u0438\u043b\u0438:<\/p>\n<pre><code>private suspend fun loadFilesInMaster(page: Page) {      log.info(\"\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0443\u0435\u043c \u0432\u043a\u043b\u0430\u0434\u043a\u0443 \u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\")   val interfaceTab = page.getByRole(       AriaRole.TAB,       Page.GetByRoleOptions().setName(\"\u0427\u0435\u0440\u0435\u0437 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\")   )   \/\/ \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u0447\u0442\u043e \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0432\u0438\u0434\u043d\u0430 \u043f\u0435\u0440\u0435\u0434 \u043a\u043b\u0438\u043a\u043e\u043c   interfaceTab.waitFor(Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE))   \/\/ \u041a\u043b\u0438\u043a\u0430\u0435\u043c \u043f\u043e \u0432\u043a\u043b\u0430\u0434\u043a\u0435 \u0438 \u0436\u0434\u0435\u043c, \u043f\u043e\u043a\u0430 \u0432\u043a\u043b\u0430\u0434\u043a\u0430 \u0441\u0442\u0430\u043d\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0439.   interfaceTab.click()   \/\/ \u0416\u0434\u0435\u043c, \u043f\u043e\u043a\u0430 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 data-state \u0441\u0442\u0430\u043d\u0435\u0442 \"active\" (\u044d\u0442\u043e \u043d\u0430\u0434\u0435\u0436\u043d\u0435\u0435, \u0447\u0435\u043c waitForLoadState)   assertThat(interfaceTab).hasAttribute(\"data-state\", \"active\",       LocatorAssertions.HasAttributeOptions().setTimeout(5000.0))   \/\/\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b   \/\/  \u0412\u0410\u0416\u041d\u041e: PlayWright \u043d\u0443\u0436\u0435\u043d \u0410\u0411\u0421\u041e\u041b\u042e\u0422\u041d\u042b\u0419 \u043f\u0443\u0442\u044c \u043a \u0444\u0430\u0439\u043b\u0443.   val folderToUpload = Paths.get(\"src\/main\/resources\/load_data\")   val absolutePath = folderToUpload.toAbsolutePath().toString()   val filePaths = java.nio.file.Files.walk(folderToUpload, 1) \/\/ 1 - \u0433\u043b\u0443\u0431\u0438\u043d\u0430 (\u0442\u043e\u043b\u044c\u043a\u043e \u0444\u0430\u0439\u043b\u044b \u0432 \u043a\u043e\u0440\u043d\u0435)       .filter { java.nio.file.Files.isRegularFile(it) } \/\/ \u0418\u0441\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0438       .map { it.toAbsolutePath() } \/\/ \u041f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u0443\u0435\u043c Path \u0432 \u0430\u0431\u0441\u043e\u043b\u044e\u0442\u043d\u044b\u0439 \u043f\u0443\u0442\u044c       .toList()       .toTypedArray()   if (filePaths.isEmpty()) {       log.warn(\"\u0412 \u043f\u0430\u043f\u043a\u0435 'load_data' \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0444\u0430\u0439\u043b\u043e\u0432 \u0434\u043b\u044f \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.\")       return \/\/ \u041f\u0440\u0435\u043a\u0440\u0430\u0449\u0430\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435, \u0435\u0441\u043b\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u043d\u0435\u0442   }   log.info(\"\u0417\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u0444\u0430\u0439\u043b\u044b \u0438\u0437 $absolutePath\")   page.setInputFiles(\"input[type=\\\"file\\\"]\", filePaths)   \/\/\u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 \u0434\u043b\u044f \u0437\u0435\u043b\u0435\u043d\u043e\u0439 \u0438\u043a\u043e\u043d\u043a\u0438 \u0443\u0441\u043f\u0435\u0445\u0430 (SVG \u0441 \u043d\u0443\u0436\u043d\u044b\u043c\u0438 \u043a\u043b\u0430\u0441\u0441\u0430\u043c\u0438)   val successIconSelector = \"svg.lucide-file-check.text-status-success\"   log.info(\"\u041e\u0436\u0438\u0434\u0430\u0435\u043c \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043a\u043e\u043d\u043a\u0438 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0439 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438.\")   page.waitForSelector(       successIconSelector,       Page.WaitForSelectorOptions().setState(WaitForSelectorState.VISIBLE)   )   log.info(\"\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0444\u0430\u0439\u043b\u043e\u0432 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0418\u043a\u043e\u043d\u043a\u0430 \u0443\u0441\u043f\u0435\u0445\u0430 \u043f\u043e\u044f\u0432\u0438\u043b\u0430\u0441\u044c.\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>\u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435 \u043c\u044b \u043d\u0435 \u044d\u043c\u0443\u043b\u0438\u0440\u0443\u0435\u043c \u0442\u0443\u0442 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f drag-and-drop, \u0430 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u043c\u0435\u0442\u043e\u0434 page.setInputFiles(&#171;input[type=\\&#187;file\\&#187;]&#187;, filePaths) \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u043f\u0443\u0442\u0435\u0439 \u043a \u0444\u0430\u0439\u043b\u0430\u043c \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u0434\u043e \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c. \u041d\u043e, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043d\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u044c \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043e\u0442\u043b\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c \u0434\u0440\u0430\u0433-\u043d-\u0434\u0440\u043e\u043f, \u0430 \u043d\u0430\u0448 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u044d\u0442\u0430 \u0442\u0438\u043f\u043e\u0432\u0430\u044f \u0445\u0438\u0442\u0440\u043e\u0441\u0442\u044c \u043d\u0435 \u0434\u043e\u043b\u0436\u043d\u0430 \u0441\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u043e\u0441\u043f\u043e\u0441\u043e\u0431\u043d\u043e\u0441\u0442\u0438 \u043d\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0430.<\/p>\n<h2>\u041a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 h\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043d\u0430 Playwright<\/h2>\n<p>\u041c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f, \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043a\u043e\u0434\u0430. \u0417\u0434\u0435\u0441\u044c \u0445\u043e\u0447\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0430\u043c \u043f\u0430\u0440\u0443 \u0441\u043a\u0440\u0438\u043d\u043e\u0432, \u043a\u0430\u043a \u044d\u0442\u043e \u0432\u0441\u0435 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f5d\/9ee\/924\/f5d9ee9248732b3853e63821f2e319cf.png\" alt=\"\u041e\u043a\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430\" title=\"\u041e\u043a\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430\" width=\"975\" height=\"916\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/f5d\/9ee\/924\/f5d9ee9248732b3853e63821f2e319cf.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f5d\/9ee\/924\/f5d9ee9248732b3853e63821f2e319cf.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u041e\u043a\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0433\u043b\u0430\u0432\u043d\u043e\u0435 \u043e\u043a\u043d\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412\u0432\u0435\u0440\u0445\u0443 \u043a\u043d\u043e\u043f\u043a\u0438 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443, \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u0442\u0430\u0431\u043b\u0438\u0447\u043a\u0443 \u0441 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u044f\u043c\u0438 \u0434\u0435\u0442\u0430\u043b\u0435\u0439 \u0444\u043b\u043e\u0443, \u0430 \u043d\u0438\u0436\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438\u0442\u043e\u0433\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u043e\u0432 &#8212; \u0441\u0441\u044b\u043b\u043a\u0438 \u043d\u0430 \u0444\u0430\u0439\u043b\u044b \u043b\u043e\u0433\u043e\u0432. \u0418\u043c\u044f \u043b\u043e\u0433\u043e\u0432 \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0438\u0437 \u0434\u0430\u0442\u044b-\u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0444\u043b\u043e\u0443, \u0430 \u043f\u043e \u043a\u043b\u0438\u043a\u0443 \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u043b\u043e\u0433 \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0435\u0433\u043e \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0449\u0438\u043a:<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/260\/e5b\/b17\/260e5bb17012117e9102e1aa1232b7e1.png\" alt=\"\u0412\u044b\u0432\u043e\u0434 \u043b\u043e\u0433\u0430\" title=\"\u0412\u044b\u0432\u043e\u0434 \u043b\u043e\u0433\u0430\" width=\"1832\" height=\"902\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/260\/e5b\/b17\/260e5bb17012117e9102e1aa1232b7e1.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/260\/e5b\/b17\/260e5bb17012117e9102e1aa1232b7e1.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>\u0412\u044b\u0432\u043e\u0434 \u043b\u043e\u0433\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0412 \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0449\u0438\u043a\u0435 \u043b\u043e\u0433 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u0432\u0432\u0435\u0440\u0445 \u043d\u043e\u0433\u0430\u043c\u0438, \u0441\u0430\u043c\u044b\u0435 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u0437\u0430\u043f\u0438\u0441\u0438 \u0441\u0432\u0435\u0440\u0445\u0443. \u042d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0443\u0434\u0430 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0439\u0442\u0438 \u043a\u043e\u0433\u0434\u0430 \u043b\u043e\u0433 \u0435\u0449\u0435 \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u0444\u043b\u043e\u0443 \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 \u0420\u0430\u0431\u043e\u0442\u0430\u0435\u0442. \u041b\u043e\u0433 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u043e\u0431\u043d\u043e\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u0436\u0434\u044b\u0435 10 \u0441\u0435\u043a\u0443\u043d\u0434, \u0438 \u0441\u0432\u0435\u0436\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043d\u0430 \u0432\u0435\u0440\u0445\u0443 \u044d\u043a\u0440\u0430\u043d\u0430 \u0430 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u0443\u043f\u043b\u044b\u0432\u0430\u0435\u0442 \u0432\u0441\u0435 \u043d\u0438\u0436\u0435 \u0438 \u043d\u0438\u0436\u0435. \u0411\u0435\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u0431\u044b \u0441\u043a\u0440\u043e\u043b\u043b\u0438\u0442\u044c \u044d\u043a\u0440\u0430\u043d \u043a\u043e\u043b\u0435\u0441\u0438\u043a\u043e\u043c \u0438\u043b\u0438 \u043f\u0435\u0440\u0435\u043c\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u043c \u043a\u0430\u0436\u0434\u044b\u0439 \u0440\u0430\u0437, \u043d\u0430 \u0434\u043b\u0438\u043d\u043d\u044b\u0445 \u043b\u043e\u0433\u0430\u0445 \u0441\u043c\u043e\u0442\u0440\u0435\u043b\u043e\u0441\u044c \u0431\u044b \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u043c\u043e\u0440\u0433\u0430\u044e\u0449\u0435, \u043a\u0430\u043a \u043c\u043d\u0435 \u043a\u0430\u0436\u0435\u0442\u0441\u044f.<\/p>\n<h2>\u041f\u043e\u0434\u0432\u0435\u0434\u0435\u043c \u0438\u0442\u043e\u0433\u0438<\/h2>\n<p>\u041c\u044b \u0432 Amvera \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u044b \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f Playwright. \u0411\u0443\u0434\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0439 \u0434\u043b\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432. \u0421\u0430\u043c\u044b\u043c \u0441\u043b\u043e\u0436\u043d\u044b\u043c \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0443 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u043a \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u044f\u044e\u0449\u0435\u043c\u0443\u0441\u044f \u0442\u0435\u043a\u0441\u0442\u0443 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0439 \u0438 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u0439. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0445\u043e\u0447\u0435\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0443\u043b\u0438\u0440\u043e\u0432\u043a\u0443 \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435, \u0447\u0442\u043e\u0431\u044b \u043a\u043b\u0438\u0435\u043d\u0442\u0430\u043c \u044f\u0441\u043d\u0435\u0435 \u0434\u043e\u043d\u0435\u0441\u0442\u0438 \u0441\u0432\u043e\u0439 \u043f\u043e\u0441\u044b\u043b. \u0410 \u043f\u043e\u0442\u043e\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043e\u0442\u043b\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c, \u043f\u043e\u0447\u0435\u043c\u0443 \u0432 \u044d\u0442\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u043d\u0430\u0447\u0430\u043b \u0442\u0435\u0441\u0442 \u043f\u0430\u0434\u0430\u0442\u044c.\u00a0<\/p>\n<p>\u041d\u043e \u0437\u0430\u0442\u043e \u0432 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435 \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043b\u043e\u0432\u0438\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0435\u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u0445 \u043d\u044e\u0430\u043d\u0441\u043e\u0432 \u0438 \u0431\u0430\u0433\u043e\u0432, \u0442\u0430\u043a \u0447\u0442\u043e \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0432\u043e \u0431\u043b\u0430\u0433\u043e.<\/p>\n<p>\u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0432\u0430\u043c \u043d\u0430\u0441\u0442\u043e\u043b\u044c\u043a\u043e \u0436\u0435 \u043e\u043a\u0430\u0436\u0435\u0442\u0441\u044f \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0439 \u044d\u0442\u0430 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e\u0431 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 Playwright \u0432 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438, \u043d\u0430 \u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043d\u0430\u043c \u0431\u044b\u043b\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0432 \u044d\u0442\u043e\u043c \u0440\u0430\u0437\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f. <\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1068330\/\">https:\/\/habr.com\/ru\/articles\/1068330\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u0412 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0440\u0430\u0437\u0431\u0435\u0440\u0443, \u043a\u0430\u043a \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Playwright. \u0418 \u043f\u043e\u043a\u0430\u0436\u0443 \u0432\u0441\u0435 \u043d\u0430 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435.\u041c\u044b \u0440\u0430\u0437\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u043c PaaS-\u043e\u0431\u043b\u0430\u043a\u043e \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0438\u043d\u0433\u0430 \u0432\u0435\u0431-\u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 Amvera. \u0418 \u043a\u043e\u0433\u0434\u0430 \u043d\u0430\u0448 \u043f\u0440\u043e\u0434\u0443\u043a\u0442 \u0441\u0442\u0430\u043b \u0441\u043b\u043e\u0436\u043d\u044b\u043c, \u043c\u044b \u0441\u0442\u043e\u043b\u043a\u043d\u0443\u043b\u0438\u0441\u044c \u0441 \u0442\u0435\u043c, \u0447\u0442\u043e \u0440\u0435\u043b\u0438\u0437\u044b \u0441\u0442\u0430\u043b\u0438 \u0437\u0430\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u0440\u043e\u0434 \u0431\u0430\u0433\u0438. \u0423 \u043d\u0430\u0441 \u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0438\u0437\u0432\u0435\u0441\u0442\u0438 \u0434\u0435\u043f\u043b\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0443 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f \u0438\u043b\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443 \u0444\u0430\u0439\u043b\u043e\u0432 \u0432 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435. \u0412 \u043e\u0442\u043b\u0438\u0447\u0438\u0435 \u043e\u0442 VPS, \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441 \u0434\u043e\u043b\u0436\u0435\u043d \u0438\u0437 \u043a\u043e\u0440\u043e\u0431\u043a\u0438 \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0431\u043e\u0440\u043a\u0443, \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 NGINX, \u0434\u043e\u043c\u0435\u043d\u043e\u0432 \u0441 SSL \u0438 \u043c\u043d\u043e\u0433\u043e \u0447\u0442\u043e \u0435\u0449\u0435, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0434\u0443\u043c\u0430\u043b \u043e\u0431 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438. \u0410 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0443\u0448\u0438\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438 \u043f\u043e\u043b\u0443\u0447\u0430\u043b \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0439 \u0431\u043e\u0442 \u0438\u043b\u0438 \u0441\u0430\u0439\u0442. \u0418 \u0432\u043e\u0442 \u044d\u0442\u043e \u201c\u043c\u043d\u043e\u0433\u043e \u0447\u0442\u043e \u0435\u0449\u0435\u201d \u0434\u043e\u043b\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0438 \u043d\u0435 \u043b\u043e\u043c\u0430\u0442\u044c\u0441\u044f. \u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u201c\u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f\u0445\u201d \u0438 \u043c\u0438\u043d\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0447\u0435\u043b\u043e\u0432\u0435\u0447\u0435\u0441\u043a\u0438\u0439 \u0444\u0430\u043a\u0442\u043e\u0440, \u043c\u044b \u0440\u0435\u0448\u0438\u043b\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u044b\u0435 \u0447\u0430\u0441\u0442\u044b\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f Playwright.\u00a0\u041f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0441 Playwright \u043c\u044b \u0440\u0430\u0437\u043c\u0435\u0441\u0442\u0438\u043b\u0438 \u0432 \u043d\u0430\u0448\u0435\u043c \u0436\u0435 \u043e\u0431\u043b\u0430\u043a\u0435 Amvera, \u043a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u044b. \u041e\u043d\u0438 \u0440\u0435\u0433\u0443\u043b\u044f\u0440\u043d\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u044e\u0442 \u043e\u0441\u043d\u043e\u0432\u043d\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0438 \u0434\u0430\u044e\u0442 \u0441\u0438\u0433\u043d\u0430\u043b, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0448\u043b\u043e \u043d\u0435 \u0442\u0430\u043a. \u0422\u0435\u043f\u0435\u0440\u044c, \u0435\u0441\u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u0438 \u043b\u043e\u043c\u0430\u0435\u0442\u0441\u044f, \u043c\u044b \u043e\u0431 \u044d\u0442\u043e\u043c \u0431\u044b\u0441\u0442\u0440\u043e \u0443\u0437\u043d\u0430\u0435\u043c \u0438 \u0447\u0438\u043d\u0438\u043c.\u00a0 \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u043f\u0440\u0438\u0432\u0435\u0434\u0443 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e, \u043a\u0430\u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Playwright, \u0447\u0442\u043e\u0431\u044b \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435.\u041c\u044b \u0432\u044b\u0431\u0440\u0430\u043b\u0438 Kotlin \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u044f\u0437\u044b\u043a\u0430 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0438 Playwright \u0432 \u0440\u043e\u043b\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u0432 \u0431\u0435\u0437\u0433\u043e\u043b\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435. \u0412\u044b\u0431\u043e\u0440 \u043e\u0431\u044a\u044f\u0441\u043d\u044f\u0435\u0442\u0441\u044f \u043f\u0440\u043e\u0441\u0442\u043e &#8212; \u044d\u0442\u043e \u044f\u0437\u044b\u043a, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d \u043d\u0430\u0448 \u0441\u0435\u0440\u0432\u0438\u0441. \u041d\u0430\u0434\u0435\u044e\u0441\u044c, \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u043c\u043e\u0436\u0435\u0442 \u0438 Java-\u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c.\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0441\u0443\u0442\u0438 \u0438 \u043d\u0430\u0447\u043d\u0435\u043c \u043f\u0438\u0441\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043d\u0430 Playwright.\u041a\u0430\u043a \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c Playwright\u0414\u043b\u044f \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 \u043d\u0430\u0448\u0438\u0445 \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u044f \u0441\u043e\u0437\u0434\u0430\u043b \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441 FlowExecutorService. \u041e\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u043e\u0433\u0438\u043a\u0443 \u0438\u043d\u0438\u0446\u0438\u0430\u0446\u0438\u0438 Playwright \u0438 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b, \u0447\u0430\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0435 \u0432 \u043a\u0430\u0436\u0434\u043e\u043c \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0412 \u0435\u0433\u043e \u043a\u043e\u043d\u0441\u0442\u0440\u0443\u043a\u0442\u043e\u0440 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0430\u0432\u0430\u0442\u044c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0438\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f:@Serviceclass FlowExecutorService(   private val context: ApplicationContext,   @param:Value(&#171;\\${playwright.headed}&#187;) private val headed: Boolean,   @param:Value(&#171;\\${amvera.test-project-name}&#187;) internal val amveraTestProjectName: String,   @param:Value(&#171;\\${amvera.username}&#187;) internal val amveraUsername: String,   @param:Value(&#171;\\${amvera.password}&#187;) internal val amveraPassword: String,   @param:Value(&#171;\\${amvera.host}&#187;) internal val amveraHost: String,   @param:Value(&#171;\\${amvera.isAmvera}&#187;) private val isAmvera: Boolean)\u041a \u043d\u0438\u043c \u043e\u0442\u043d\u043e\u0441\u044f\u0442\u0441\u044f \u043a\u0440\u0435\u0434\u044b (\u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f, \u043e\u0442 \u043b\u0438\u0446\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c) \u0438 \u0445\u043e\u0441\u0442 \u041b\u041a (\u0442\u0435\u0441\u0442 \u043c\u043e\u0436\u043d\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430 \u043f\u0440\u043e\u0434\u043e\u0432\u043e\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435, \u0442\u0430\u043a \u0438 \u043d\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0441\u0442\u0435\u0439\u0434\u0436\u0430\u0445)\u00a0 \u043a\u0430\u043a \u0432 \u0431\u0435\u0437\u0433\u043e\u043b\u043e\u0432\u043e\u043c \u0440\u0435\u0436\u0438\u043c\u0435, \u0442\u0430\u043a \u0438 \u0441 \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c \u043e\u043a\u043d\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430.\u0414\u0430\u043b\u044c\u0448\u0435 \u043c\u044b \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 PlayWright \u0438 \u0441\u0442\u0430\u0440\u0442\u0443\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0425\u0440\u043e\u043c:\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u043b\u0430\u0441\u0442\u044c \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0434\u043b\u044f \u0444\u043e\u043d\u043e\u0432\u044b\u0445 \u0437\u0430\u0434\u0430\u0447private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)\/\/ \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u0435\u043c \u043e\u0434\u0438\u043d \u0440\u0430\u0437 \u043f\u0440\u0438 \u0441\u0442\u0430\u0440\u0442\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u0430. by lazy \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u0438 \u043f\u0435\u0440\u0432\u043e\u043c \u0432\u044b\u0437\u043e\u0432\u0435.private val playwright by lazy { Playwright.create() }\/\/ \u041e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u0434\u043b\u044f \u0432\u0441\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.\/\/ \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 100-200 \u041c\u0411 \u043e\u043f\u0435\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0439 \u043f\u0430\u043c\u044f\u0442\u0438 \u043d\u0430 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0444\u043b\u043e\u0443.private val browser by lazy {   playwright.chromium().launch(       com.microsoft.playwright.BrowserType.LaunchOptions()           .setHeadless(!headed)           .setArgs(listOf(&#171;&#8212;start-maximized&#187;))   )}val activeTasks = ConcurrentHashMap&lt;String, String&gt;()\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0443\u043a\u0430\u0437\u0430\u043d\u0438\u0435 \u0440\u0430\u0437\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043e\u043a\u043d\u043e \u043d\u0430 \u0432\u0435\u0441\u044c \u044d\u043a\u0440\u0430\u043d. \u0422\u0430\u043a \u0436\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0443 \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u044f\u0445 Gradle:dependencies {   implementation(&#171;com.microsoft.playwright:playwright:1.56.0&#187;)}\u0418 \u0438\u043d\u0438\u0446\u0438\u0438\u0440\u0443\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443 \u043f\u043e \u0437\u0430\u043a\u0430\u0447\u043a\u0435 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432 \u0442\u0430\u043c \u0436\u0435, \u0432 build.gradle.kts\/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0437\u0430\u0434\u0430\u0447\u0443 \u0434\u043b\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u0432tasks.register(&#171;installPlaywright&#187;) {   group = &#171;verification&#187;   description = &#171;Downloads Playwright browsers into the project directory&#187;   doLast {       project.javaexec {\/\/ \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c classpath \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u0433\u0434\u0435 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c playwright.jar           classpath = sourceSets[&#171;main&#187;].runtimeClasspath           mainClass.set(&#171;com.microsoft.playwright.CLI&#187;)           args = listOf(&#171;install&#187;)\/\/ \u0423\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u043f\u0443\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0438 build\/libs\/\/ \u041c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c layout.buildDirectory, \u0447\u0442\u043e\u0431\u044b \u043f\u0443\u0442\u044c \u0431\u044b\u043b \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u043c \u043d\u0430 \u043b\u044e\u0431\u043e\u0439 \u041e\u0421           val browsersPath = File(project.layout.buildDirectory.asFile.get(), &#171;libs\/pw-browsers&#187;)           environment(&#171;PLAYWRIGHT_BROWSERS_PATH&#187;, browsersPath.absolutePath)        }   }\u041f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0438\u0441\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f graceful shutdown. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u044e \u0432 application.yml \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044e:server: #  \u0412\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0433\u0440\u0430\u0446\u0438\u043e\u0437\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 shutdown: graceful\u0418 \u043f\u0440\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u0432 \u043d\u0430\u0448\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0441\u0435\u0440\u0432\u0438\u0441\u0435 FlowExecutorService \u043c\u0435\u0442\u043e\u0434, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u0441\u0432\u043e\u0431\u043e\u0436\u0434\u0430\u0442\u044c \u0440\u0435\u0441\u0443\u0440\u0441\u044b \u043f\u0435\u0440\u0435\u0434 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u0443 @PreDestroy@PreDestroyfun shutdownService() {   log.info(&#171;\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u0435\u0440\u0432\u0438\u0441\u0430: \u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0445 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u0440\u0443\u0442\u0438\u043d&#8230;&#187;)   browser.close()   playwright.close()   serviceScope.cancel()\u041a\u0430\u043a \u0432 Playwright \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0442\u0435\u043a\u0430\u043d\u0438\u0435 \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0444\u043b\u043e\u0443?\u0421\u0430\u043c\u044b\u043c \u043f\u0440\u043e\u0441\u0442\u044b\u043c \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0438\u0441\u0430\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u0442\u0430\u043f\u0430 \u0442\u0435\u0441\u0442\u0430 \u0432 \u043b\u043e\u0433\u0438. \u0414\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b \u0441 \u0438\u043c\u0435\u043d\u0435\u043c, \u0432\u043a\u043b\u044e\u0447\u0430\u044e\u0449\u0438\u043c \u0434\u0430\u0442\u0443-\u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0438 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0444\u043b\u043e\u0443. \u042d\u0442\u0438 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b\u044b \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u043e\u043d\u0438\u0440\u0443\u044e\u0442\u0441\u044f \u0432 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0439 \u043f\u0430\u043f\u043a\u0435 \u0438 \u0438\u0445 \u0441\u043f\u0438\u0441\u043e\u043a \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u043f\u043e \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u044e \u044d\u0442\u043e\u0439 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443 \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442\u0430 \u0433\u043b\u0430\u0432\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u201c\/\u201d\u0427\u0442\u043e\u0431\u044b \u043f\u0430\u043f\u043a\u0430 \u0441 \u043b\u043e\u0433\u0430\u043c\u0438 \u043d\u0435 \u0440\u0430\u0437\u0440\u0430\u0441\u0442\u0430\u043b\u0430\u0441\u044c \u0431\u0435\u0437\u0433\u0440\u0430\u043d\u0438\u0447\u043d\u043e \u0441\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0435\u043c, \u043f\u0440\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u043c\u0435\u0442\u043e\u0434 \u043e\u0447\u0438\u0441\u0442\u043a\u0438 \u0441\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 (\u0441\u0442\u0430\u0440\u0448\u0435 30 \u0434\u043d\u0435\u0439 \u043e\u0442 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0434\u0430\u0442\u044b):private fun cleanOldLogs() {   \/\/ \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u0440\u0435\u0434\u0443 \u0447\u0435\u0440\u0435\u0437 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f   val isAmvera = System.getenv(&#171;IS_AMVERA&#187;)?.toBoolean() ?: true   val logDirPath = System.getenv(&#171;LOG_PATH&#187;)?: &#171;test_results&#187;   log.warn(&#171;isAmvera $isAmvera&#187;)   log.warn(&#171;logDirPath $logDirPath&#187;)   val logDir = File(logDirPath)   if (!logDir.exists()) return   val monthAgo = Instant.now().minus(30, ChronoUnit.DAYS)   var deletedCount = 0   logDir.listFiles()?.forEach { file -&gt;       if (file.isFile &amp;&amp; Instant.ofEpochMilli(file.lastModified()).isBefore(monthAgo)) {           if (file.delete()) {               log.info(&#171;\u0423\u0434\u0430\u043b\u0435\u043d \u0441\u0442\u0430\u0440\u044b\u0439 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b: ${file.name}&#187;)               deletedCount++           }       }   }   if (deletedCount &gt; 0) {       log.info(&#171;\u041e\u0447\u0438\u0441\u0442\u043a\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430. \u0423\u0434\u0430\u043b\u0435\u043d\u043e \u0444\u0430\u0439\u043b\u043e\u0432: $deletedCount&#187;)   } else {       log.info(&#171;\u0421\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 \u0434\u043b\u044f \u0443\u0434\u0430\u043b\u0435\u043d\u0438\u044f \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.&#187;)   }}\u041d\u0430 \u0437\u0430\u0433\u043b\u0430\u0432\u043d\u043e\u043c \u044d\u043d\u0434\u043f\u043e\u0439\u043d\u0442\u0435 \u043c\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u043c \u0441\u043f\u0438\u0441\u043e\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0444\u043b\u043e\u0443 \u0430\u0432\u0442\u043e\u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u043e\u0434 \u043d\u0438\u043c\u0438 \u0441\u043f\u0438\u0441\u043e\u043a \u043b\u043e\u0433\u043e\u0432 &#8212; \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u044d\u0442\u0438\u0445 \u0444\u043b\u043e\u0443 \u0432 \u043f\u0440\u043e\u0448\u043b\u043e\u043c \u0441 \u0438\u0442\u043e\u0433\u043e\u0432\u044b\u043c \u0441\u0442\u0430\u0442\u0443\u0441\u043e\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f &#8212; \u201c\u0423\u0441\u043f\u0435\u0448\u043d\u043e\u201d, \u201c\u0421 \u043e\u0448\u0438\u0431\u043a\u043e\u0439\u201d \u0438\u043b\u0438 \u201c\u0412 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0435\u201d \u2013 \u0434\u043b\u044f \u0441\u0432\u0435\u0436\u0435\u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0445 \u0444\u043b\u043e\u0443. \u0421\u0442\u0430\u0442\u0443\u0441\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0445\u0440\u0430\u043d\u044f\u0442\u0441\u044f \u0432 \u043f\u0440\u0435\u0434\u0435\u043b\u0430\u0445 \u0441\u0435\u0441\u0441\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0435\u043a\u0442\u0430, \u043f\u0440\u0438 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u043a\u0435 \u0434\u043b\u044f \u0441\u0442\u0430\u0440\u044b\u0445 \u043b\u043e\u0433\u043e\u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u044b \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c\u0441\u044f \u043d\u0435 \u0431\u0443\u0434\u0443\u0442, \u043d\u043e \u0438 \u043f\u043e\u0442\u0440\u0435\u0431\u043d\u043e\u0441\u0442\u0438 \u0438\u0445 \u0432\u0438\u0434\u0435\u0442\u044c \u0432 \u044d\u0442\u043e\u043c \u0441\u043f\u0438\u0441\u043a\u0435 \u043e\u0431\u044b\u0447\u043d\u043e \u0443\u0436\u0435 \u043d\u0435 \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442.\u0414\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043c\u0435\u043d\u0430\u043c\u0438 \u043b\u043e\u0433-\u0444\u0430\u0439\u043b\u043e\u0432 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e logback \u0447\u0435\u0440\u0435\u0437 logback-spring.xml \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0430\u043f\u043f\u0435\u043d\u0434\u0435\u0440\u0430 SIFT \u0434\u043b\u044f \u043b\u043e\u0433\u0433\u0435\u0440\u0430 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c FLOW_FILE_LOGGER (\u0438 \u0438\u043c\u0435\u043d\u043d\u043e \u044d\u0442\u043e\u0442 \u043b\u043e\u0433\u0433\u0435\u0440 \u044f \u0431\u0443\u0434\u0443 \u0443\u043a\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0432 \u043a\u043b\u0430\u0441\u0441\u0430\u0445 \u0441 \u0434\u0435\u043a\u043b\u0430\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043c\u0435\u0442\u043e\u0434\u043e\u0432 \u0441\u0430\u043c\u043e\u0433\u043e \u0444\u043b\u043e\u0443. \u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u043b\u043e\u0433\u0438 \u043d\u0435 \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u044e\u0442 \u0444\u0430\u0439\u043b\u043e\u0432, \u0438\u0434\u0443\u0442 \u043f\u0440\u043e\u0441\u0442\u043e \u0432 STDOUT):&lt;configuration&gt;   &lt;appender name=&#187;STDOUT&#187; class=&#187;ch.qos.logback.core.ConsoleAppender&#187;&gt;       &lt;encoder&gt;           &lt;pattern&gt;%d{HH:mm:ss} %highlight(%-4level) %-50msg &#8212; %-15logger{15}%n&lt;\/pattern&gt;       &lt;\/encoder&gt;   &lt;\/appender&gt;   &lt;appender name=&#187;SIFT&#187; class=&#187;ch.qos.logback.classic.sift.SiftingAppender&#187;&gt;       &lt;discriminator&gt;           &lt;key&gt;logFileName&lt;\/key&gt;           &lt;defaultValue&gt;application&lt;\/defaultValue&gt;       &lt;\/discriminator&gt;       &lt;sift&gt;           &lt;appender name=&#187;FILE-${logFileName}&#187; class=&#187;ch.qos.logback.core.FileAppender&#187;&gt;               &lt;file&gt;${LOG_PATH:-test_results}\/${logFileName}.log&lt;\/file&gt;               &lt;append&gt;true&lt;\/append&gt;               &lt;encoder&gt;                   &lt;pattern&gt;%d{HH:mm:ss} %-4level %-50msg%n&lt;\/pattern&gt;               &lt;\/encoder&gt;           &lt;\/appender&gt;       &lt;\/sift&gt;   &lt;\/appender&gt;   &lt;root level=&#187;INFO&#187;&gt;       &lt;appender-ref ref=&#187;STDOUT&#187; \/&gt;   &lt;\/root&gt;   &lt;logger name=&#187;FLOW_FILE_LOGGER&#187; level=&#187;INFO&#187; additivity=&#187;false&#187;&gt;       &lt;appender-ref ref=&#187;STDOUT&#187; \/&gt;       &lt;appender-ref ref=&#187;SIFT&#187; \/&gt;   &lt;\/logger&gt;&lt;\/configuration&gt;\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f Playwright\u0414\u043b\u044f Playwright \u0441\u0438\u0441\u0442\u0435\u043c\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 \u043d\u0435 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0438\u0430\u043b\u044c\u043d\u0430, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043c\u044b \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0435 \u043f\u043e\u043b\u044f \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043b\u043e\u0433\u0438\u043d\u0430 \u0438 \u0437\u0430\u0442\u0435\u043c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 \u201c\u0412\u043e\u0439\u0442\u0438\u201d. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u044f \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043b \u0432 \u0441\u0435\u0440\u0432\u0438\u0441\u0435 FlowExecutorService \u043c\u0435\u0442\u043e\u0434 pushLogin:internal suspend fun pushLogin(page: Page) {   \/\/ \u041d\u0430\u0447\u0438\u043d\u0430\u0435\u043c \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e \u0441 https:\/\/cloud.amvera.ru   \/\/ Playwright \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438 \u043f\u0435\u0440\u0435\u0439\u0434\u0435\u0442 \u043d\u0430 https:\/\/id.amvera.ru\/   log.warn(&#171;\u0422\u0415\u0421\u0422\u0418\u0420\u0423\u042e $amveraHost&#187;)   page.navigate(amveraHost)   \/\/ \u0412\u0432\u043e\u0434\u0438\u043c \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c   log.info(&#171;\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043b\u043e\u0433\u0438\u043d: $amveraUsername&#187;)   page.fill(&#171;#username&#187;, amveraUsername)   page.fill(&#171;#password&#187;, amveraPassword)   \/\/ \u041d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043a\u043d\u043e\u043f\u043a\u0443 &#171;\u0412\u043e\u0439\u0442\u0438&#187;   page.click(&#171;#kc-login&#187;)   \/\/ \u0416\u0434\u0435\u043c \u043f\u0435\u0440\u0435\u0430\u0434\u0440\u0435\u0441\u0430\u0446\u0438\u0438 \u043d\u0430 \u0434\u0430\u0448\u0431\u043e\u0440\u0434   \/\/ \u0416\u0434\u0435\u043c, \u043f\u043e\u043a\u0430 URL \u043f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u0435\u0442 \u0431\u044b\u0442\u044c id.amvera.ru \u0438 \u0432\u0435\u0440\u043d\u0435\u0442\u0441\u044f \u043d\u0430 cloud.amvera.ru   page.waitForURL(&#171;${amveraHost}\/projects&#187;)   log.info(&#171;\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043b\u043e\u0433\u0438\u043d\u0438\u043b\u0438\u0441\u044c. \u0422\u0435\u043a\u0443\u0449\u0430\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430: ${page.title()}&#187;)   takeScreenshot(page, &#171;login_success.png&#187;)   log.info(&#171;\u0421\u043a\u0440\u0438\u043d\u0448\u043e\u0442 \u043b\u043e\u0433\u0438\u043d\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d \u043a\u0430\u043a login.png&#187;)\u0412 \u043c\u0435\u0442\u043e\u0434 \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u0442\u0438\u043f\u0430 Page, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0432 \u043c\u0435\u0442\u043e\u0434\u0435 executeFlow() \u043d\u0430\u0448\u0435\u0433\u043e \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u043a\u043b\u0430\u0441\u0441\u0430 FlowExecutorService \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:\/*** \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u0444\u043b\u043e\u0443 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f* *\/fun executeFlow(flow: IFlow): Job {   \/\/ \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u043d \u043a \u043e\u0434\u043d\u043e\u043c\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u043c\u0443 \u043f\u043e\u0442\u043e\u043a\u0443 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0444\u043b\u043e\u0443   return serviceScope.launch {       val timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern(&#171;MM-dd-HH-mm-ss&#187;))       val fileName = &#171;${timestamp} ${flow.name}.log&#187;       activeTasks[fileName] = &#171;Init&#187;       val contextOptions = Browser.NewContextOptions().apply {           if (headed) {               \/\/ \u0412 \u0440\u0435\u0436\u0438\u043c\u0435 \u0441 \u043e\u043a\u043d\u043e\u043c \u2014 \u0440\u0430\u0437\u0440\u0435\u0448\u0430\u0435\u043c \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0443 \u0441\u0430\u043c\u043e\u043c\u0443 \u0440\u0435\u0448\u0430\u0442\u044c \u0440\u0430\u0437\u043c\u0435\u0440 (\u043f\u043e\u0434\u0445\u0432\u0430\u0442\u0438\u0442 maximized)               setViewportSize(null)           } else {               \/\/ \u0412&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-490288","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/490288","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=490288"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/490288\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=490288"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=490288"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=490288"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}