4141use Playwright \Locator \LocatorInterface ;
4242use Playwright \Locator \Options \GetByRoleOptions ;
4343use Playwright \Locator \Options \LocatorOptions ;
44+ use Playwright \Locator \RoleSelectorBuilder ;
4445use Playwright \Network \Request ;
4546use Playwright \Network \Response ;
4647use Playwright \Network \ResponseInterface ;
6162use Playwright \Page \Options \WaitForResponseOptions ;
6263use Playwright \Page \Options \WaitForSelectorOptions ;
6364use Playwright \Page \Options \WaitForUrlOptions ;
65+ use Playwright \Regex ;
6466use Playwright \Screenshot \ScreenshotHelper ;
6567use Playwright \Transport \TransportInterface ;
6668use Psr \Log \LoggerInterface ;
@@ -210,33 +212,50 @@ public function locator(string $selector, array|LocatorOptions $options = []): L
210212 /**
211213 * @param array<string, mixed>|LocatorOptions $options
212214 */
213- public function getByAltText (string $ text , array |LocatorOptions $ options = []): LocatorInterface
215+ public function getByAltText (string | Regex $ text , array |LocatorOptions $ options = []): LocatorInterface
214216 {
215- return $ this ->locator (\sprintf ('[alt="%s"] ' , $ text ), $ this ->normalizeLocatorOptions ($ options ));
217+ $ opts = $ this ->normalizeLocatorOptions ($ options );
218+ $ exact = self ::extractExact ($ opts );
219+ $ selector = self ::buildAttrSelector ('alt ' , $ text , $ exact );
220+
221+ return $ this ->locator ($ selector , $ opts );
216222 }
217223
218224 /**
219225 * @param array<string, mixed>|LocatorOptions $options
220226 */
221- public function getByLabel (string $ text , array |LocatorOptions $ options = []): LocatorInterface
227+ public function getByLabel (string | Regex $ text , array |LocatorOptions $ options = []): LocatorInterface
222228 {
223- return $ this ->locator (\sprintf ('label:text-is("%s") >> nth=0 ' , $ text ), $ this ->normalizeLocatorOptions ($ options ));
229+ $ opts = $ this ->normalizeLocatorOptions ($ options );
230+ $ exact = self ::extractExact ($ opts );
231+ $ selector = self ::buildLabelSelector ($ text , $ exact );
232+
233+ return $ this ->locator ($ selector , $ opts );
224234 }
225235
226236 /**
227237 * @param array<string, mixed>|LocatorOptions $options
228238 */
229- public function getByPlaceholder (string $ text , array |LocatorOptions $ options = []): LocatorInterface
239+ public function getByPlaceholder (string | Regex $ text , array |LocatorOptions $ options = []): LocatorInterface
230240 {
231- return $ this ->locator (\sprintf ('[placeholder="%s"] ' , $ text ), $ this ->normalizeLocatorOptions ($ options ));
241+ $ opts = $ this ->normalizeLocatorOptions ($ options );
242+ $ exact = self ::extractExact ($ opts );
243+ $ selector = self ::buildAttrSelector ('placeholder ' , $ text , $ exact );
244+
245+ return $ this ->locator ($ selector , $ opts );
232246 }
233247
234248 /**
235249 * @param array<string, mixed>|GetByRoleOptions $options
236250 */
237251 public function getByRole (string $ role , array |GetByRoleOptions $ options = []): LocatorInterface
238252 {
239- return $ this ->locator ($ role , $ this ->normalizeGetByRoleOptions ($ options ));
253+ $ options = GetByRoleOptions::from ($ options );
254+ $ optionsArray = $ options ->toArray ();
255+ $ selector = RoleSelectorBuilder::buildSelector ($ role , $ optionsArray );
256+ $ locatorOptions = RoleSelectorBuilder::filterLocatorOptions ($ optionsArray );
257+
258+ return $ this ->locator ($ selector , $ locatorOptions );
240259 }
241260
242261 /**
@@ -250,17 +269,25 @@ public function getByTestId(string $testId, array|LocatorOptions $options = []):
250269 /**
251270 * @param array<string, mixed>|LocatorOptions $options
252271 */
253- public function getByText (string $ text , array |LocatorOptions $ options = []): LocatorInterface
272+ public function getByText (string | Regex $ text , array |LocatorOptions $ options = []): LocatorInterface
254273 {
255- return $ this ->locator (\sprintf ('text="%s" ' , $ text ), $ this ->normalizeLocatorOptions ($ options ));
274+ $ opts = $ this ->normalizeLocatorOptions ($ options );
275+ $ exact = self ::extractExact ($ opts );
276+ $ selector = self ::buildTextSelector ($ text , $ exact );
277+
278+ return $ this ->locator ($ selector , $ opts );
256279 }
257280
258281 /**
259282 * @param array<string, mixed>|LocatorOptions $options
260283 */
261- public function getByTitle (string $ text , array |LocatorOptions $ options = []): LocatorInterface
284+ public function getByTitle (string | Regex $ text , array |LocatorOptions $ options = []): LocatorInterface
262285 {
263- return $ this ->locator (\sprintf ('[title="%s"] ' , $ text ), $ this ->normalizeLocatorOptions ($ options ));
286+ $ opts = $ this ->normalizeLocatorOptions ($ options );
287+ $ exact = self ::extractExact ($ opts );
288+ $ selector = self ::buildAttrSelector ('title ' , $ text , $ exact );
289+
290+ return $ this ->locator ($ selector , $ opts );
264291 }
265292
266293 /**
@@ -581,16 +608,6 @@ private function normalizeLocatorOptions(array|LocatorOptions $options): array
581608 return LocatorOptions::from ($ options )->toArray ();
582609 }
583610
584- /**
585- * @param array<string, mixed>|GetByRoleOptions $options
586- *
587- * @return array<string, mixed>
588- */
589- private function normalizeGetByRoleOptions (array |GetByRoleOptions $ options ): array
590- {
591- return GetByRoleOptions::from ($ options )->toArray ();
592- }
593-
594611 /**
595612 * @param array<string, mixed> $params
596613 *
@@ -1074,6 +1091,56 @@ private static function normalizeForPage(string $expression): string
10741091 return $ expression ;
10751092 }
10761093
1094+ /**
1095+ * @param array<string, mixed> $options
1096+ */
1097+ private static function extractExact (array &$ options ): bool
1098+ {
1099+ $ exact = (bool ) ($ options ['exact ' ] ?? false );
1100+ unset($ options ['exact ' ]);
1101+
1102+ return $ exact ;
1103+ }
1104+
1105+ private static function buildTextSelector (string |Regex $ text , bool $ exact ): string
1106+ {
1107+ if ($ text instanceof Regex) {
1108+ return \sprintf ('internal:text=%s ' , $ text ->pattern );
1109+ }
1110+
1111+ if ($ exact ) {
1112+ return \sprintf ('internal:text="%s" ' , addcslashes ($ text , '\\" ' ));
1113+ }
1114+
1115+ return \sprintf ('internal:text=/%s/i ' , preg_quote ($ text , '/ ' ));
1116+ }
1117+
1118+ private static function buildAttrSelector (string $ attr , string |Regex $ text , bool $ exact ): string
1119+ {
1120+ if ($ text instanceof Regex) {
1121+ return \sprintf ('internal:attr=[%s=%s] ' , $ attr , $ text ->pattern );
1122+ }
1123+
1124+ if ($ exact ) {
1125+ return \sprintf ('internal:attr=[%s="%s"] ' , $ attr , addcslashes ($ text , '\\" ' ));
1126+ }
1127+
1128+ return \sprintf ('internal:attr=[%s=/%s/i] ' , $ attr , preg_quote ($ text , '/ ' ));
1129+ }
1130+
1131+ private static function buildLabelSelector (string |Regex $ text , bool $ exact ): string
1132+ {
1133+ if ($ text instanceof Regex) {
1134+ return \sprintf ('internal:label=%s ' , $ text ->pattern );
1135+ }
1136+
1137+ if ($ exact ) {
1138+ return \sprintf ('internal:label="%s" ' , addcslashes ($ text , '\\" ' ));
1139+ }
1140+
1141+ return \sprintf ('internal:label=/%s/i ' , preg_quote ($ text , '/ ' ));
1142+ }
1143+
10771144 private static function isFunctionLike (string $ s ): bool
10781145 {
10791146 return (bool ) preg_match ('/^((async\s+)?function\b|\([^)]*\)\s*=>|[A-Za-z_$][A-Za-z0-9_$]*\s*=>|async\s*\([^)]*\)\s*=>)/ ' , $ s );
0 commit comments