@@ -65,6 +65,19 @@ public function toBeVisible(): void
6565 );
6666 }
6767
68+ public function toBeEmpty (): void
69+ {
70+ if (!$ this ->subject instanceof LocatorInterface) {
71+ throw new \InvalidArgumentException ('toBeEmpty() can only be used with LocatorInterface ' );
72+ }
73+
74+ $ this ->retryAssertion (
75+ fn () => $ this ->subject ->isEmpty (),
76+ !$ this ->negated ,
77+ $ this ->negated ? 'Locator is empty, but expected not to be. ' : 'Locator is not empty. '
78+ );
79+ }
80+
6881 public function toHaveText (string $ text ): void
6982 {
7083 if (!$ this ->subject instanceof LocatorInterface) {
@@ -250,6 +263,19 @@ public function toBeFocused(): void
250263 );
251264 }
252265
266+ public function toHaveFocus (): void
267+ {
268+ if (!$ this ->subject instanceof LocatorInterface) {
269+ throw new \InvalidArgumentException ('toHaveFocus() can only be used with LocatorInterface ' );
270+ }
271+
272+ $ this ->retryAssertion (
273+ fn () => (bool ) $ this ->subject ->evaluate ('(element) => document.activeElement === element ' ),
274+ !$ this ->negated ,
275+ $ this ->negated ? 'Locator has focus, but expected not to have. ' : 'Locator has not focus. '
276+ );
277+ }
278+
253279 public function toHaveTitle (string $ title ): void
254280 {
255281 if (!$ this ->subject instanceof PageInterface) {
@@ -296,6 +322,51 @@ function () use ($url): string {
296322 );
297323 }
298324
325+ public function toHaveClass (string |array $ class ): void
326+ {
327+ if (!$ this ->subject instanceof LocatorInterface) {
328+ throw new \InvalidArgumentException ('toHaveClass() can only be used with LocatorInterface ' );
329+ }
330+
331+ $ expectedClasses = is_array ($ class ) ? $ class : [$ class ];
332+
333+ $ this ->retryAssertion (
334+ function () use ($ expectedClasses ) {
335+ \assert ($ this ->subject instanceof LocatorInterface);
336+ $ elementClass = $ this ->subject ->getAttribute ('class ' );
337+ if (null === $ elementClass ) {
338+ return false ;
339+ }
340+ $ classes = explode (' ' , $ elementClass );
341+ foreach ($ expectedClasses as $ expectedClass ) {
342+ if (!in_array ($ expectedClass , $ classes , true )) {
343+ return false ;
344+ }
345+ }
346+
347+ return true ;
348+ },
349+ !$ this ->negated ,
350+ $ this ->negated
351+ ? 'Locator has the specified class(es), but expected not to. '
352+ : 'Locator does not have the specified class(es). ' ,
353+ function () use ($ expectedClasses ): string {
354+ \assert ($ this ->subject instanceof LocatorInterface);
355+ $ actual = (string ) $ this ->subject ->getAttribute ('class ' );
356+ $ expected = implode (' ' , $ expectedClasses );
357+
358+ return $ this ->negated
359+ ? \sprintf ('Expected class list not to contain %s. Actual: %s ' , \json_encode ($ expected ), \json_encode ($ actual ))
360+ : \sprintf ('Expected class list to contain %s. Actual: %s ' , \json_encode ($ expected ), \json_encode ($ actual ));
361+ }
362+ );
363+ }
364+
365+ public function toHaveId (string $ id ): void
366+ {
367+ $ this ->toHaveAttribute ('id ' , $ id );
368+ }
369+
299370 /**
300371 * Core retry assertion mechanism with configurable timeout and polling.
301372 */
0 commit comments