简体   繁体   中英

Angular ViewEncapsulation.ShadowDom elements in Protractor give StaleElementExceptionError

I have an iframe inside an angular component, into which I am injecting a component X dynamically using component factory (appending the native element to the iframe's body). The reason I am doing this is because I need media queries to function in the component. Without ViewEncapsulation.ShadowDom the component injected inside the frame will be 'style-less'.

Until now everything is fine and dandy and the feature works exactly as I want. However, during e2e testing I am not able to access any elements inside the shadow-root , I have tried multiple methods like document.querySelector('component-id').shadowRoot.querySelector('element-id') and even libraries and work arounds that are mentioned here .

Everything results in the same error

StaleElementReferenceError: stale element reference: stale element not found

To summarize,

  • An angular component with Shadow Dom encapsulation is being injected into an iframe
  • Cannot access the elements inside the shadowroot of the injected component using all the available methods

I realize this answer might not address your particular question, and that I am running the risk of losing rep due to downvotes, but I would like to suggest a change in architecture, based on my comments to the question above.

Since you have full control over the content of the iframe I would suggest setting up Angular routing in such a way as to allow you to specify a route for the iframe that would display your simple component, while keeping the rest of the application's routes intact.

Abstract

So assuming you have your app's routes set up normally, with a top level component (usually app.component ) which hosts the <router-outlet> tag, I would create a new top level component (let's call it app-plain.component ) which contains only <router-outlet></router-outlet> in its template, and nothing else. I would set up the app to use this component as the bootstrap component (instead of app.component ), then I would change the route configuration to move all existing routes as children of a new blank path route ( '' ) with the old app.component as its component.

Finally I would set up a new route with some path (say iframe ) that would use the component you wish to display in the iframe as its component (let's call it iframe.component ).

This would allow you to put an element such as this <iframe src="/iframe"></iframe> anywhere in your components, and have it display just your iframe.component .

Implementation

Assuming your existing route configuration looks like this:

const routes = [
  { path: '', pathMatch: 'full', redirectTo: 'home' },
  { path: 'home', component: HomeComponent },
  // ... more routes here
];

I would modify the route configuration as follows:

const newRoutes = [
  { path: 'iframe', component: IframeComponent },
  {
    path: '', component: AppComponent, children: routes // <-- the old routes
  }
];

As mentioned, I would create a new plain top level component called app-plain.component with just a <router-outlet> in its template, and set it up as the module's bootstrap component:

@NgModule({
  declarations: [...],
  imports: [...],
  providers: [...],
  bootstrap: [AppPlainComponent] // <-- this is the change
})
export class AppModule { }

NOTE: Since the bootstrap component has changed, you have to specify its selector in your index.html too (instead of app-root which it is by default)

Demo/Sample

I've created a github repo with this solution concept.

I could not make a StackBlitz, because the iframe never finishes loading when I try, here's my failed attempt .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM