mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: rework assert dialog (#28043)
This commit is contained in:
		
							parent
							
								
									5f527fedb1
								
							
						
					
					
						commit
						b004c1a0a7
					
				| @ -14,16 +14,18 @@ | |||||||
|  * limitations under the License. |  * limitations under the License. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| export const highlightCSS = ` | :host { | ||||||
|  |   font-size: 13px; | ||||||
|  |   font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; | ||||||
|  |   color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| x-pw-tooltip { | x-pw-tooltip { | ||||||
|   backdrop-filter: blur(5px); |   backdrop-filter: blur(5px); | ||||||
|   background-color: white; |   background-color: white; | ||||||
|   color: #222; |  | ||||||
|   border-radius: 6px; |   border-radius: 6px; | ||||||
|   box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); |   box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); | ||||||
|   display: none; |   display: none; | ||||||
|   font-family: 'Dank Mono', 'Operator Mono', Inconsolata, 'Fira Mono', |  | ||||||
|               'SF Mono', Monaco, 'Droid Sans Mono', 'Source Code Pro', monospace; |  | ||||||
|   font-size: 12.8px; |   font-size: 12.8px; | ||||||
|   font-weight: normal; |   font-weight: normal; | ||||||
|   left: 0; |   left: 0; | ||||||
| @ -31,11 +33,27 @@ x-pw-tooltip { | |||||||
|   max-width: 600px; |   max-width: 600px; | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: 0; |   top: 0; | ||||||
|  |   padding: 4px; | ||||||
| } | } | ||||||
| x-pw-tooltip-body { | 
 | ||||||
|   align-items: center; | x-pw-dialog { | ||||||
|   padding: 3.2px 5.12px 3.2px; |   background-color: white; | ||||||
|  |   pointer-events: auto; | ||||||
|  |   border-radius: 6px; | ||||||
|  |   box-shadow: 0 0.5rem 1.2rem rgba(0,0,0,.3); | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   position: absolute; | ||||||
|  |   min-width: 500px; | ||||||
|  |   min-height: 200px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | x-pw-dialog-body { | ||||||
|  |   display: flex; | ||||||
|  |   flex-direction: column; | ||||||
|  |   flex: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| x-pw-highlight { | x-pw-highlight { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: 0; |   top: 0; | ||||||
| @ -43,6 +61,7 @@ x-pw-highlight { | |||||||
|   width: 0; |   width: 0; | ||||||
|   height: 0; |   height: 0; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-action-point { | x-pw-action-point { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   width: 20px; |   width: 20px; | ||||||
| @ -52,20 +71,24 @@ x-pw-action-point { | |||||||
|   margin: -10px 0 0 -10px; |   margin: -10px 0 0 -10px; | ||||||
|   z-index: 2; |   z-index: 2; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-separator { | x-pw-separator { | ||||||
|   height: 1px; |   height: 1px; | ||||||
|   margin: 6px 9px; |   margin: 6px 9px; | ||||||
|   background: rgb(148 148 148 / 90%); |   background: rgb(148 148 148 / 90%); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-gripper { | x-pw-tool-gripper { | ||||||
|   height: 28px; |   height: 28px; | ||||||
|   width: 24px; |   width: 24px; | ||||||
|   margin: 2px 0; |   margin: 2px 0; | ||||||
|   cursor: grab; |   cursor: grab; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-gripper:active { | x-pw-tool-gripper:active { | ||||||
|   cursor: grabbing; |   cursor: grabbing; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-gripper > x-div { | x-pw-tool-gripper > x-div { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 100%; |   height: 100%; | ||||||
| @ -79,11 +102,20 @@ x-pw-tool-gripper > x-div { | |||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z' /></svg>"); | ||||||
|   background-color: #555555; |   background-color: #555555; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | x-pw-tool-label { | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   margin-left: 10px; | ||||||
|  |   user-select: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| x-pw-tools-list { | x-pw-tools-list { | ||||||
|   display: flex; |   display: flex; | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   border-bottom: 1px solid #dddddd; |   border-bottom: 1px solid #dddddd; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item { | x-pw-tool-item { | ||||||
|   pointer-events: auto; |   pointer-events: auto; | ||||||
|   cursor: pointer; |   cursor: pointer; | ||||||
| @ -91,9 +123,11 @@ x-pw-tool-item { | |||||||
|   width: 28px; |   width: 28px; | ||||||
|   border-radius: 3px; |   border-radius: 3px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item:not(.disabled):hover { | x-pw-tool-item:not(.disabled):hover { | ||||||
|   background-color: hsl(0, 0%, 86%); |   background-color: hsl(0, 0%, 86%); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item > x-div { | x-pw-tool-item > x-div { | ||||||
|   width: 100%; |   width: 100%; | ||||||
|   height: 100%; |   height: 100%; | ||||||
| @ -105,45 +139,52 @@ x-pw-tool-item > x-div { | |||||||
|   mask-size: 16px; |   mask-size: 16px; | ||||||
|   background-color: #3a3a3a; |   background-color: #3a3a3a; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.disabled > x-div { | x-pw-tool-item.disabled > x-div { | ||||||
|   background-color: rgba(97, 97, 97, 0.5); |   background-color: rgba(97, 97, 97, 0.5); | ||||||
|   cursor: default; |   cursor: default; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.active > x-div { | x-pw-tool-item.active > x-div { | ||||||
|   background-color: #006ab1; |   background-color: #006ab1; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.record.active > x-div { | x-pw-tool-item.record.active > x-div { | ||||||
|   background-color: #a1260d; |   background-color: #a1260d; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.accept > x-div { | x-pw-tool-item.accept > x-div { | ||||||
|   background-color: #388a34; |   background-color: #388a34; | ||||||
| } | } | ||||||
| x-pw-tool-item.cancel > x-div { | 
 | ||||||
|   background-color: #e51400; |  | ||||||
| } |  | ||||||
| x-pw-tool-item.record > x-div { | x-pw-tool-item.record > x-div { | ||||||
|   /* codicon: circle-large-filled */ |   /* codicon: circle-large-filled */ | ||||||
|   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>"); |   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>"); | ||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M8 1a6.8 6.8 0 0 1 1.86.253 6.899 6.899 0 0 1 3.083 1.805 6.903 6.903 0 0 1 1.804 3.083C14.916 6.738 15 7.357 15 8s-.084 1.262-.253 1.86a6.9 6.9 0 0 1-.704 1.674 7.157 7.157 0 0 1-2.516 2.509 6.966 6.966 0 0 1-1.668.71A6.984 6.984 0 0 1 8 15a6.984 6.984 0 0 1-1.86-.246 7.098 7.098 0 0 1-1.674-.711 7.3 7.3 0 0 1-1.415-1.094 7.295 7.295 0 0 1-1.094-1.415 7.098 7.098 0 0 1-.71-1.675A6.985 6.985 0 0 1 1 8c0-.643.082-1.262.246-1.86a6.968 6.968 0 0 1 .711-1.667 7.156 7.156 0 0 1 2.509-2.516 6.895 6.895 0 0 1 1.675-.704A6.808 6.808 0 0 1 8 1z'/></svg>"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.pick-locator > x-div { | x-pw-tool-item.pick-locator > x-div { | ||||||
|   /* codicon: inspect */ |   /* codicon: inspect */ | ||||||
|   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>"); |   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>"); | ||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M1 3l1-1h12l1 1v6h-1V3H2v8h5v1H2l-1-1V3zm14.707 9.707L9 6v9.414l2.707-2.707h4zM10 13V8.414l3.293 3.293h-2L10 13z'/></svg>"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.assert > x-div { | x-pw-tool-item.assert > x-div { | ||||||
|   /* codicon: check-all */ |   /* codicon: check-all */ | ||||||
|   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>"); |   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>"); | ||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path fill-rule='evenodd' clip-rule='evenodd' d='M15.62 3.596L7.815 12.81l-.728-.033L4 8.382l.754-.53 2.744 3.907L14.917 3l.703.596z'/><path fill-rule='evenodd' clip-rule='evenodd' d='M7.234 8.774l4.386-5.178L10.917 3l-4.23 4.994.547.78zm-1.55.403l.548.78-.547-.78zm-1.617 1.91l.547.78-.799.943-.728-.033L0 8.382l.754-.53 2.744 3.907.57-.672z'/></svg>"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.accept > x-div { | x-pw-tool-item.accept > x-div { | ||||||
|   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); |   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); | ||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/></svg>"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-tool-item.cancel > x-div { | x-pw-tool-item.cancel > x-div { | ||||||
|   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); |   -webkit-mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); | ||||||
|   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); |   mask-image: url("data:image/svg+xml;utf8,<svg width='16' height='16' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' fill='currentColor'><path d='M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-overlay { | x-pw-overlay { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: 0; |   top: 0; | ||||||
| @ -152,21 +193,52 @@ x-pw-overlay { | |||||||
|   background: transparent; |   background: transparent; | ||||||
|   pointer-events: auto; |   pointer-events: auto; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-overlay x-pw-tools-list { | x-pw-overlay x-pw-tools-list { | ||||||
|   background-color: #ffffffdd; |   background-color: #ffffffdd; | ||||||
|   box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; |   box-shadow: rgba(0, 0, 0, 0.1) 0px 5px 5px; | ||||||
|   border-radius: 3px; |   border-radius: 3px; | ||||||
|   border-bottom: none; |   border-bottom: none; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| x-pw-overlay x-pw-tool-item { | x-pw-overlay x-pw-tool-item { | ||||||
|   margin: 2px; |   margin: 2px; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | input.locator-editor { | ||||||
|  |   display: flex; | ||||||
|  |   padding: 10px; | ||||||
|  |   flex: none; | ||||||
|  |   border: none; | ||||||
|  |   border-bottom: 1px solid #dddddd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | input.locator-editor:focus, | ||||||
|  | textarea.text-editor:focus { | ||||||
|  |   outline: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | textarea.text-editor { | ||||||
|  |   font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; | ||||||
|  |   flex: auto; | ||||||
|  |   border: none; | ||||||
|  |   padding: 10px; | ||||||
|  |   color: #333; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| x-div { | x-div { | ||||||
|   display: block; |   display: block; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | x-spacer { | ||||||
|  |   flex: auto; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| * { | * { | ||||||
|   box-sizing: border-box; |   box-sizing: border-box; | ||||||
| } | } | ||||||
|  | 
 | ||||||
| *[hidden] { | *[hidden] { | ||||||
|   display: none !important; |   display: none !important; | ||||||
| }`; | } | ||||||
| @ -19,7 +19,7 @@ import type { ParsedSelector } from '../../utils/isomorphic/selectorParser'; | |||||||
| import type { InjectedScript } from './injectedScript'; | import type { InjectedScript } from './injectedScript'; | ||||||
| import { asLocator } from '../../utils/isomorphic/locatorGenerators'; | import { asLocator } from '../../utils/isomorphic/locatorGenerators'; | ||||||
| import type { Language } from '../../utils/isomorphic/locatorGenerators'; | import type { Language } from '../../utils/isomorphic/locatorGenerators'; | ||||||
| import { highlightCSS } from './highlight.css'; | import highlightCSS from './highlight.css?inline'; | ||||||
| 
 | 
 | ||||||
| type HighlightEntry = { | type HighlightEntry = { | ||||||
|   targetElement: Element, |   targetElement: Element, | ||||||
| @ -34,9 +34,6 @@ type HighlightEntry = { | |||||||
| export type HighlightOptions = { | export type HighlightOptions = { | ||||||
|   tooltipText?: string; |   tooltipText?: string; | ||||||
|   color?: string; |   color?: string; | ||||||
|   anchorGetter?: (element: Element) => DOMRect; |  | ||||||
|   toolbar?: Element[]; |  | ||||||
|   interactive?: boolean; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export class Highlight { | export class Highlight { | ||||||
| @ -63,7 +60,12 @@ export class Highlight { | |||||||
|     this._glassPaneElement.style.pointerEvents = 'none'; |     this._glassPaneElement.style.pointerEvents = 'none'; | ||||||
|     this._glassPaneElement.style.display = 'flex'; |     this._glassPaneElement.style.display = 'flex'; | ||||||
|     this._glassPaneElement.style.backgroundColor = 'transparent'; |     this._glassPaneElement.style.backgroundColor = 'transparent'; | ||||||
| 
 |     for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mousemove', 'mouseleave', 'focus', 'scroll']) { | ||||||
|  |       this._glassPaneElement.addEventListener(eventName, e => { | ||||||
|  |         e.stopPropagation(); | ||||||
|  |         e.stopImmediatePropagation(); | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|     this._actionPointElement = document.createElement('x-pw-action-point'); |     this._actionPointElement = document.createElement('x-pw-action-point'); | ||||||
|     this._actionPointElement.setAttribute('hidden', 'true'); |     this._actionPointElement.setAttribute('hidden', 'true'); | ||||||
|     this._glassPaneShadow = this._glassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' }); |     this._glassPaneShadow = this._glassPaneElement.attachShadow({ mode: this._isUnderTest ? 'open' : 'closed' }); | ||||||
| @ -145,26 +147,12 @@ export class Highlight { | |||||||
|       let tooltipElement; |       let tooltipElement; | ||||||
|       if (options.tooltipText) { |       if (options.tooltipText) { | ||||||
|         tooltipElement = this._injectedScript.document.createElement('x-pw-tooltip'); |         tooltipElement = this._injectedScript.document.createElement('x-pw-tooltip'); | ||||||
|  |         this._glassPaneShadow.appendChild(tooltipElement); | ||||||
|  |         const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; | ||||||
|  |         tooltipElement.textContent = options.tooltipText + suffix; | ||||||
|         tooltipElement.style.top = '0'; |         tooltipElement.style.top = '0'; | ||||||
|         tooltipElement.style.left = '0'; |         tooltipElement.style.left = '0'; | ||||||
|         tooltipElement.style.display = 'flex'; |         tooltipElement.style.display = 'flex'; | ||||||
|         tooltipElement.style.flexDirection = 'column'; |  | ||||||
|         tooltipElement.style.alignItems = 'start'; |  | ||||||
|         if (options.interactive) |  | ||||||
|           tooltipElement.style.pointerEvents = 'auto'; |  | ||||||
| 
 |  | ||||||
|         if (options.toolbar) { |  | ||||||
|           const toolbar = this._injectedScript.document.createElement('x-pw-tools-list'); |  | ||||||
|           tooltipElement.appendChild(toolbar); |  | ||||||
|           for (const toolbarElement of options.toolbar) |  | ||||||
|             toolbar.appendChild(toolbarElement); |  | ||||||
|         } |  | ||||||
|         const bodyElement = this._injectedScript.document.createElement('x-pw-tooltip-body'); |  | ||||||
|         tooltipElement.appendChild(bodyElement); |  | ||||||
| 
 |  | ||||||
|         this._glassPaneShadow.appendChild(tooltipElement); |  | ||||||
|         const suffix = elements.length > 1 ? ` [${i + 1} of ${elements.length}]` : ''; |  | ||||||
|         bodyElement.textContent = options.tooltipText + suffix; |  | ||||||
|       } |       } | ||||||
|       this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText }); |       this._highlightEntries.push({ targetElement: elements[i], tooltipElement, highlightElement, tooltipText: options.tooltipText }); | ||||||
|     } |     } | ||||||
| @ -176,25 +164,7 @@ export class Highlight { | |||||||
|         continue; |         continue; | ||||||
| 
 | 
 | ||||||
|       // Position tooltip, if any.
 |       // Position tooltip, if any.
 | ||||||
|       const tooltipWidth = entry.tooltipElement.offsetWidth; |       const { anchorLeft, anchorTop } = this.tooltipPosition(entry.box, entry.tooltipElement); | ||||||
|       const tooltipHeight = entry.tooltipElement.offsetHeight; |  | ||||||
|       const totalWidth = this._glassPaneElement.offsetWidth; |  | ||||||
|       const totalHeight = this._glassPaneElement.offsetHeight; |  | ||||||
| 
 |  | ||||||
|       const anchorBox = options.anchorGetter ? options.anchorGetter(entry.targetElement) : entry.box; |  | ||||||
|       let anchorLeft = anchorBox.left; |  | ||||||
|       if (anchorLeft + tooltipWidth > totalWidth - 5) |  | ||||||
|         anchorLeft = totalWidth - tooltipWidth - 5; |  | ||||||
|       let anchorTop = anchorBox.bottom + 5; |  | ||||||
|       if (anchorTop + tooltipHeight > totalHeight - 5) { |  | ||||||
|         // If can't fit below, either position above...
 |  | ||||||
|         if (anchorBox.top > tooltipHeight + 5) { |  | ||||||
|           anchorTop = anchorBox.top - tooltipHeight - 5; |  | ||||||
|         } else { |  | ||||||
|           // Or on top in case of large element
 |  | ||||||
|           anchorTop = totalHeight - 5 - tooltipHeight; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       entry.tooltipTop = anchorTop; |       entry.tooltipTop = anchorTop; | ||||||
|       entry.tooltipLeft = anchorLeft; |       entry.tooltipLeft = anchorLeft; | ||||||
|     } |     } | ||||||
| @ -219,6 +189,33 @@ export class Highlight { | |||||||
|         console.error('Highlight box for test: ' + JSON.stringify({ x: box.x, y: box.y, width: box.width, height: box.height })); // eslint-disable-line no-console
 |         console.error('Highlight box for test: ' + JSON.stringify({ x: box.x, y: box.y, width: box.width, height: box.height })); // eslint-disable-line no-console
 | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   firstBox(): DOMRect | undefined { | ||||||
|  |     return this._highlightEntries[0]?.box; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   tooltipPosition(box: DOMRect, tooltipElement: HTMLElement) { | ||||||
|  |     const tooltipWidth = tooltipElement.offsetWidth; | ||||||
|  |     const tooltipHeight = tooltipElement.offsetHeight; | ||||||
|  |     const totalWidth = this._glassPaneElement.offsetWidth; | ||||||
|  |     const totalHeight = this._glassPaneElement.offsetHeight; | ||||||
|  | 
 | ||||||
|  |     let anchorLeft = box.left; | ||||||
|  |     if (anchorLeft + tooltipWidth > totalWidth - 5) | ||||||
|  |       anchorLeft = totalWidth - tooltipWidth - 5; | ||||||
|  |     let anchorTop = box.bottom + 5; | ||||||
|  |     if (anchorTop + tooltipHeight > totalHeight - 5) { | ||||||
|  |       // If can't fit below, either position above...
 | ||||||
|  |       if (box.top > tooltipHeight + 5) { | ||||||
|  |         anchorTop = box.top - tooltipHeight - 5; | ||||||
|  |       } else { | ||||||
|  |         // Or on top in case of large element
 | ||||||
|  |         anchorTop = totalHeight - 5 - tooltipHeight; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return { anchorLeft, anchorTop }; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   private _highlightIsUpToDate(elements: Element[], tooltipText: string | undefined): boolean { |   private _highlightIsUpToDate(elements: Element[], tooltipText: string | undefined): boolean { | ||||||
|     if (elements.length !== this._highlightEntries.length) |     if (elements.length !== this._highlightEntries.length) | ||||||
|       return false; |       return false; | ||||||
|  | |||||||
| @ -20,10 +20,12 @@ import { generateSelector } from '../injected/selectorGenerator'; | |||||||
| import type { Point } from '../../common/types'; | import type { Point } from '../../common/types'; | ||||||
| import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; | import type { Mode, OverlayState, UIState } from '@recorder/recorderTypes'; | ||||||
| import { Highlight, type HighlightOptions } from '../injected/highlight'; | import { Highlight, type HighlightOptions } from '../injected/highlight'; | ||||||
| import { enclosingElement, isInsideScope, parentElementOrShadowHost } from './domUtils'; | import { isInsideScope } from './domUtils'; | ||||||
| import { elementText } from './selectorUtils'; | import { elementText } from './selectorUtils'; | ||||||
| import { escapeWithQuotes, normalizeWhiteSpace } from '../../utils/isomorphic/stringUtils'; |  | ||||||
| import { asLocator } from '../../utils/isomorphic/locatorGenerators'; | import { asLocator } from '../../utils/isomorphic/locatorGenerators'; | ||||||
|  | import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; | ||||||
|  | import { parseSelector } from '@isomorphic/selectorParser'; | ||||||
|  | import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; | ||||||
| 
 | 
 | ||||||
| interface RecorderDelegate { | interface RecorderDelegate { | ||||||
|   performAction?(action: actions.Action): Promise<void>; |   performAction?(action: actions.Action): Promise<void>; | ||||||
| @ -442,217 +444,187 @@ class RecordActionTool implements RecorderTool { | |||||||
| 
 | 
 | ||||||
| class TextAssertionTool implements RecorderTool { | class TextAssertionTool implements RecorderTool { | ||||||
|   private _hoverHighlight: HighlightModel | null = null; |   private _hoverHighlight: HighlightModel | null = null; | ||||||
|   private _selectionHighlight: HighlightModel | null = null; |   private _action: actions.AssertAction | null = null; | ||||||
|   private _selectionText: { selectedText: string, fullText: string } | null = null; |   private _dialogElement: HTMLElement | null = null; | ||||||
|   private _inputHighlight: HighlightModel | null = null; |  | ||||||
|   private _acceptButton: HTMLElement; |   private _acceptButton: HTMLElement; | ||||||
|   private _cancelButton: HTMLElement; |   private _cancelButton: HTMLElement; | ||||||
|  |   private _keyboardListener: ((event: KeyboardEvent) => void) | undefined; | ||||||
| 
 | 
 | ||||||
|   constructor(private _recorder: Recorder) { |   constructor(private _recorder: Recorder) { | ||||||
|     this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); |     this._acceptButton = this._recorder.document.createElement('x-pw-tool-item'); | ||||||
|  |     this._acceptButton.title = 'Accept'; | ||||||
|     this._acceptButton.classList.add('accept'); |     this._acceptButton.classList.add('accept'); | ||||||
|     this._acceptButton.appendChild(this._recorder.document.createElement('x-div')); |     this._acceptButton.appendChild(this._recorder.document.createElement('x-div')); | ||||||
|     this._acceptButton.addEventListener('click', () => this._commitAction()); |     this._acceptButton.addEventListener('click', () => this._commit()); | ||||||
| 
 | 
 | ||||||
|     this._cancelButton = this._recorder.document.createElement('x-pw-tool-item'); |     this._cancelButton = this._recorder.document.createElement('x-pw-tool-item'); | ||||||
|  |     this._cancelButton.title = 'Close'; | ||||||
|     this._cancelButton.classList.add('cancel'); |     this._cancelButton.classList.add('cancel'); | ||||||
|     this._cancelButton.appendChild(this._recorder.document.createElement('x-div')); |     this._cancelButton.appendChild(this._recorder.document.createElement('x-div')); | ||||||
|     this._cancelButton.addEventListener('click', () => this._cancelAction()); |     this._cancelButton.addEventListener('click', () => this._closeDialog()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   cursor() { |   cursor() { | ||||||
|     return 'text'; |     return 'pointer'; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   cleanup() { |   cleanup() { | ||||||
|  |     this._closeDialog(); | ||||||
|     this._hoverHighlight = null; |     this._hoverHighlight = null; | ||||||
|     this._selectionHighlight = null; |  | ||||||
|     this._selectionText = null; |  | ||||||
|     this._inputHighlight = null; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onClick(event: MouseEvent) { |   onClick(event: MouseEvent) { | ||||||
|  |     if (!this._dialogElement) | ||||||
|  |       this._showDialog(); | ||||||
|     consumeEvent(event); |     consumeEvent(event); | ||||||
|     const selection = this._recorder.document.getSelection(); |  | ||||||
|     if (event.detail === 1 && selection && !selection.toString() && !this._inputHighlight) { |  | ||||||
|       const target = this._recorder.deepEventTarget(event); |  | ||||||
|       selection.selectAllChildren(target); |  | ||||||
|       this._updateSelectionHighlight(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onMouseDown(event: MouseEvent) { |  | ||||||
|     const target = this._recorder.deepEventTarget(event); |  | ||||||
|     if (['INPUT', 'TEXTAREA'].includes(target.nodeName)) { |  | ||||||
|       this._recorder.injectedScript.window.getSelection()?.empty(); |  | ||||||
|       this._inputHighlight = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); |  | ||||||
|       this._showHighlight(true); |  | ||||||
|       consumeEvent(event); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     this._inputHighlight = null; |  | ||||||
|     this._hoverHighlight = null; |  | ||||||
|     this._updateSelectionHighlight(); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onMouseUp(event: MouseEvent) { |  | ||||||
|     this._updateSelectionHighlight(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onMouseMove(event: MouseEvent) { |   onMouseMove(event: MouseEvent) { | ||||||
|     const selection = this._recorder.document.getSelection(); |     if (this._dialogElement) | ||||||
|     if (selection && selection.toString()) { |  | ||||||
|       this._updateSelectionHighlight(); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     if (this._inputHighlight || event.buttons) |  | ||||||
|       return; |       return; | ||||||
|     const target = this._recorder.deepEventTarget(event); |     const target = this._recorder.deepEventTarget(event); | ||||||
|     if (this._hoverHighlight?.elements[0] === target) |     if (this._hoverHighlight?.elements[0] === target) | ||||||
|       return; |       return; | ||||||
|     this._hoverHighlight = elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; |     this._hoverHighlight = target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA' || elementText(new Map(), target).full ? { elements: [target], selector: '' } : null; | ||||||
|     this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); |     this._recorder.updateHighlight(this._hoverHighlight, true, { color: '#8acae480' }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   onDragStart(event: DragEvent) { |  | ||||||
|     consumeEvent(event); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onKeyDown(event: KeyboardEvent) { |   onKeyDown(event: KeyboardEvent) { | ||||||
|     if (event.key === 'Escape') { |     if (event.key === 'Escape') | ||||||
|       const selection = this._recorder.document.getSelection(); |  | ||||||
|       if (selection && selection.toString()) |  | ||||||
|         this._resetSelectionAndHighlight(); |  | ||||||
|       else |  | ||||||
|       this._recorder.delegate.setMode?.('recording'); |       this._recorder.delegate.setMode?.('recording'); | ||||||
|     consumeEvent(event); |     consumeEvent(event); | ||||||
|       return; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|     if (event.key === 'Enter') { |   private _generateAction(): actions.AssertAction | null { | ||||||
|       this._commitAction(); |     const target = this._hoverHighlight?.elements[0]; | ||||||
|       consumeEvent(event); |     if (!target) | ||||||
|       return; |       return null; | ||||||
|     } |     if (target.nodeName === 'INPUT' || target.nodeName === 'TEXTAREA') { | ||||||
| 
 |       const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName }); | ||||||
|     // Only allow keys that control text selection.
 |       if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes((target as HTMLInputElement).type.toLowerCase())) { | ||||||
|     if (!['ArrowLeft', 'ArrowUp', 'ArrowRight', 'ArrowDown', 'Shift', 'Control', 'Meta', 'Alt', 'AltGraph'].includes(event.key)) { |  | ||||||
|       consumeEvent(event); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onKeyUp(event: KeyboardEvent) { |  | ||||||
|     consumeEvent(event); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   onScroll(event: Event) { |  | ||||||
|     this._hoverHighlight = null; |  | ||||||
|     this._showHighlight(false); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private _generateAction(): actions.Action | null { |  | ||||||
|     if (this._inputHighlight) { |  | ||||||
|       const target = this._inputHighlight.elements[0] as HTMLInputElement; |  | ||||||
|       if (target.nodeName === 'INPUT' && ['checkbox', 'radio'].includes(target.type.toLowerCase())) { |  | ||||||
|         return { |         return { | ||||||
|           name: 'assertChecked', |           name: 'assertChecked', | ||||||
|           selector: this._inputHighlight.selector, |           selector, | ||||||
|           signals: [], |           signals: [], | ||||||
|           // Interestingly, inputElement.checked is reversed inside this event handler.
 |           // Interestingly, inputElement.checked is reversed inside this event handler.
 | ||||||
|           checked: !(target as HTMLInputElement).checked, |           checked: (target as HTMLInputElement).checked, | ||||||
|         }; |         }; | ||||||
|       } else { |       } else { | ||||||
|         return { |         return { | ||||||
|           name: 'assertValue', |           name: 'assertValue', | ||||||
|           selector: this._inputHighlight.selector, |           selector, | ||||||
|           signals: [], |           signals: [], | ||||||
|           value: target.value, |           value: (target as HTMLInputElement).value, | ||||||
|         }; |         }; | ||||||
|       } |       } | ||||||
|     } else if (this._selectionText && this._selectionHighlight) { |     } else { | ||||||
|  |       const { selector } = generateSelector(this._recorder.injectedScript, target, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }); | ||||||
|       return { |       return { | ||||||
|         name: 'assertText', |         name: 'assertText', | ||||||
|         selector: this._selectionHighlight.selector, |         selector, | ||||||
|         signals: [], |         signals: [], | ||||||
|         text: this._selectionText.selectedText, |         text: target.textContent!, | ||||||
|         substring: this._selectionText.fullText !== this._selectionText.selectedText, |         substring: true, | ||||||
|       }; |       }; | ||||||
|     } |     } | ||||||
|     return null; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private _generateActionPreview() { |   private _renderValue(action: actions.Action) { | ||||||
|     const action = this._generateAction(); |  | ||||||
|     // TODO: support other languages, maybe unify with code generator?
 |  | ||||||
|     if (action?.name === 'assertText') |     if (action?.name === 'assertText') | ||||||
|       return `expect(${asLocator(this._recorder.state.language, action.selector)}).${action.substring ? 'toContainText' : 'toHaveText'}(${escapeWithQuotes(action.text)})`; |       return normalizeWhiteSpace(action.text); | ||||||
|     if (action?.name === 'assertChecked') |     if (action?.name === 'assertChecked') | ||||||
|       return `expect(${asLocator(this._recorder.state.language, action.selector)})${action.checked ? '' : '.not'}.toBeChecked()`; |       return String(action.checked); | ||||||
|     if (action?.name === 'assertValue') { |     if (action?.name === 'assertValue') | ||||||
|       const assertion = action.value ? `toHaveValue(${escapeWithQuotes(action.value)})` : `toBeEmpty()`; |       return action.value; | ||||||
|       return `expect(${asLocator(this._recorder.state.language, action.selector)}).${assertion}`; |  | ||||||
|     } |  | ||||||
|     return ''; |     return ''; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private _commitAction() { |   private _commit() { | ||||||
|     const action = this._generateAction(); |     if (!this._action || !this._dialogElement) | ||||||
|     if (action) { |       return; | ||||||
|       this._resetSelectionAndHighlight(); |     this._closeDialog(); | ||||||
|       this._recorder.delegate.recordAction?.(action); |     this._recorder.delegate.recordAction?.(this._action); | ||||||
|     this._recorder.delegate.setMode?.('recording'); |     this._recorder.delegate.setMode?.('recording'); | ||||||
|   } |   } | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   private _cancelAction() { |   private _showDialog() { | ||||||
|     this._resetSelectionAndHighlight(); |     const target = this._hoverHighlight?.elements[0]; | ||||||
|   } |     if (!target) | ||||||
| 
 |  | ||||||
|   private _resetSelectionAndHighlight() { |  | ||||||
|     this._selectionHighlight = null; |  | ||||||
|     this._selectionText = null; |  | ||||||
|     this._inputHighlight = null; |  | ||||||
|     this._recorder.injectedScript.window.getSelection()?.empty(); |  | ||||||
|     this._recorder.updateHighlight(null, false); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private _updateSelectionHighlight() { |  | ||||||
|     if (this._inputHighlight) |  | ||||||
|       return; |       return; | ||||||
|     const selection = this._recorder.document.getSelection(); |     this._action = this._generateAction(); | ||||||
|     const selectedText = normalizeWhiteSpace(selection?.toString() || ''); |     if (!this._action) | ||||||
|     let highlight: HighlightModel | null = null; |  | ||||||
|     if (selection && selection.focusNode && selection.anchorNode && selectedText) { |  | ||||||
|       const focusElement = enclosingElement(selection.focusNode); |  | ||||||
|       let lcaElement = focusElement ? enclosingElement(selection.anchorNode) : undefined; |  | ||||||
|       while (lcaElement && !isInsideScope(lcaElement, focusElement)) |  | ||||||
|         lcaElement = parentElementOrShadowHost(lcaElement); |  | ||||||
|       highlight = lcaElement ? generateSelector(this._recorder.injectedScript, lcaElement, { testIdAttributeName: this._recorder.state.testIdAttributeName, forTextExpect: true }) : null; |  | ||||||
|     } |  | ||||||
|     const fullText = highlight ? normalizeWhiteSpace(elementText(new Map(), highlight.elements[0]).full) : ''; |  | ||||||
|     const selectionText = highlight ? { selectedText, fullText } : null; |  | ||||||
|     if (highlight?.selector === this._selectionHighlight?.selector && this._selectionText?.fullText === selectionText?.fullText && this._selectionText?.selectedText === selectionText?.selectedText) |  | ||||||
|       return; |       return; | ||||||
|     this._selectionHighlight = highlight; |  | ||||||
|     this._selectionText = selectionText; |  | ||||||
|     this._showHighlight(true); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   private _showHighlight(userGesture: boolean) { |     this._dialogElement = this._recorder.document.createElement('x-pw-dialog'); | ||||||
|     const options: HighlightOptions = { |     this._keyboardListener = (event: KeyboardEvent) => { | ||||||
|       color: '#6fdcbd38', |       if (event.key === 'Escape') { | ||||||
|       tooltipText: this._generateActionPreview(), |         this._closeDialog(); | ||||||
|       toolbar: [this._acceptButton, this._cancelButton], |         return; | ||||||
|       interactive: true, |       } | ||||||
|  |       if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) { | ||||||
|  |         if (this._dialogElement) | ||||||
|  |           this._commit(); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|     }; |     }; | ||||||
|     if (this._inputHighlight) { |     this._recorder.document.addEventListener('keydown', this._keyboardListener, true); | ||||||
|       this._recorder.updateHighlight(this._inputHighlight, userGesture, options); |     const toolbarElement = this._recorder.document.createElement('x-pw-tools-list'); | ||||||
|     } else { |     toolbarElement.appendChild(this._createLabel(this._action)); | ||||||
|       options.anchorGetter = (e: Element) => this._recorder.document.getSelection()?.getRangeAt(0)?.getBoundingClientRect() || e.getBoundingClientRect(); |     toolbarElement.appendChild(this._recorder.document.createElement('x-spacer')); | ||||||
|       this._recorder.updateHighlight(this._selectionHighlight, userGesture, options); |     toolbarElement.appendChild(this._acceptButton); | ||||||
|  |     toolbarElement.appendChild(this._cancelButton); | ||||||
|  | 
 | ||||||
|  |     this._dialogElement.appendChild(toolbarElement); | ||||||
|  |     const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); | ||||||
|  |     const locatorElement = this._recorder.document.createElement('input'); | ||||||
|  |     locatorElement.classList.add('locator-editor'); | ||||||
|  |     locatorElement.value = asLocator(this._recorder.state.language, this._action.selector); | ||||||
|  |     locatorElement.addEventListener('input', () => { | ||||||
|  |       if (this._action) { | ||||||
|  |         const selector = locatorOrSelectorAsSelector(this._recorder.state.language, locatorElement.value, this._recorder.state.testIdAttributeName); | ||||||
|  |         const model: HighlightModel = { | ||||||
|  |           selector, | ||||||
|  |           elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document), | ||||||
|  |         }; | ||||||
|  |         this._action.selector = selector; | ||||||
|  |         this._recorder.updateHighlight(model, true); | ||||||
|       } |       } | ||||||
|  |     }); | ||||||
|  |     const textElement = this._recorder.document.createElement('textarea'); | ||||||
|  |     textElement.value = this._renderValue(this._action); | ||||||
|  |     textElement.classList.add('text-editor'); | ||||||
|  | 
 | ||||||
|  |     textElement.addEventListener('input', () => { | ||||||
|  |       if (this._action?.name === 'assertText') | ||||||
|  |         this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); | ||||||
|  |       if (this._action?.name === 'assertChecked') | ||||||
|  |         this._action.checked = textElement.value === 'true'; | ||||||
|  |       if (this._action?.name === 'assertValue') | ||||||
|  |         this._action.value = textElement.value; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     bodyElement.appendChild(locatorElement); | ||||||
|  |     bodyElement.appendChild(textElement); | ||||||
|  |     this._dialogElement.appendChild(bodyElement); | ||||||
|  |     this._recorder.highlight.appendChild(this._dialogElement); | ||||||
|  |     const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); | ||||||
|  |     this._dialogElement.style.top = position.anchorTop + 'px'; | ||||||
|  |     this._dialogElement.style.left = position.anchorLeft + 'px'; | ||||||
|  |     textElement.focus(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _createLabel(action: actions.AssertAction) { | ||||||
|  |     const labelElement = this._recorder.document.createElement('x-pw-tool-label'); | ||||||
|  |     labelElement.textContent = action.name === 'assertText' ? 'Assert text' : action.name === 'assertValue' ? 'Assert value' : 'Assert checked'; | ||||||
|  |     return labelElement; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private _closeDialog() { | ||||||
|  |     if (!this._dialogElement) | ||||||
|  |       return; | ||||||
|  |     this._dialogElement.remove(); | ||||||
|  |     this._recorder.document.removeEventListener('keydown', this._keyboardListener!); | ||||||
|  |     this._dialogElement = null; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -114,6 +114,7 @@ export type AssertCheckedAction = ActionBase & { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction; | export type Action = ClickAction | CheckAction | ClosesPageAction | OpenPageAction | UncheckAction | FillAction | NavigateAction | PressAction | SelectAction | SetInputFilesAction | AssertTextAction | AssertValueAction | AssertCheckedAction; | ||||||
|  | export type AssertAction = AssertCheckedAction | AssertValueAction | AssertTextAction; | ||||||
| 
 | 
 | ||||||
| // Signals.
 | // Signals.
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Pavel Feldman
						Pavel Feldman