Best Selenium code snippet using org.openqa.selenium.grid.node.Node.drain
Source:SauceNode.java
...131 Require.nonNull("Registration secret", registrationSecret);132 this.healthCheck = healthCheck == null ?133 () -> new HealthCheck.Result(134 isDraining() ? DRAINING : UP,135 String.format("%s is %s", uri, isDraining() ? "draining" : "up")) :136 healthCheck;137 this.currentSessions = CacheBuilder.newBuilder()138 .expireAfterAccess(sessionTimeout)139 .ticker(ticker)140 .removalListener((RemovalListener<SessionId, SessionSlot>) notification -> {141 // Attempt to stop the session142 LOG.log(Debug.getDebugLogLevel(), "Stopping session {0}", notification.getKey().toString());143 SessionSlot slot = notification.getValue();144 if (!slot.isAvailable()) {145 slot.stop();146 }147 })148 .build();149 this.tempFileSystems = CacheBuilder.newBuilder()150 .expireAfterAccess(sessionTimeout)151 .ticker(ticker)152 .removalListener((RemovalListener<SessionId, TemporaryFilesystem>) notification -> {153 TemporaryFilesystem tempFS = notification.getValue();154 tempFS.deleteTemporaryFiles();155 tempFS.deleteBaseDir();156 })157 .build();158 Regularly sessionCleanup = new Regularly("Session Cleanup Node: " + externalUri);159 sessionCleanup.submit(currentSessions::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));160 Regularly tmpFileCleanup = new Regularly("TempFile Cleanup Node: " + externalUri);161 tmpFileCleanup.submit(tempFileSystems::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));162 Regularly regularHeartBeat = new Regularly("Heartbeat Node: " + externalUri);163 regularHeartBeat.submit(() -> bus.fire(new NodeHeartBeatEvent(getStatus())), heartbeatPeriod,164 heartbeatPeriod);165 bus.addListener(SessionClosedEvent.listener(id -> {166 // Listen to session terminated events so we know when to fire the NodeDrainComplete event167 if (this.isDraining()) {168 int done = pendingSessions.decrementAndGet();169 if (done <= 0) {170 LOG.info("Firing node drain complete message");171 bus.fire(new NodeDrainComplete(this.getId()));172 }173 }174 }));175 Runtime.getRuntime().addShutdownHook(new Thread(this::stopAllSessions));176 new JMXHelper().register(this);177 }178 public static SauceNode.Builder builder(179 Tracer tracer,180 EventBus bus,181 URI uri,182 URI gridUri,183 Secret registrationSecret) {184 return new SauceNode.Builder(tracer, bus, uri, gridUri, registrationSecret);185 }186 @Override187 public boolean isReady() {188 return bus.isReady();189 }190 @VisibleForTesting191 public int getCurrentSessionCount() {192 // It seems wildly unlikely we'll overflow an int193 return Math.toIntExact(currentSessions.size());194 }195 @ManagedAttribute(name = "MaxSessions")196 public int getMaxSessionCount() {197 return maxSessionCount;198 }199 @ManagedAttribute(name = "Status")200 public Availability getAvailability() {201 return isDraining() ? DRAINING : UP;202 }203 @ManagedAttribute(name = "TotalSlots")204 public int getTotalSlots() {205 return factories.size();206 }207 @ManagedAttribute(name = "UsedSlots")208 public long getUsedSlots() {209 return factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();210 }211 @ManagedAttribute(name = "Load")212 public float getLoad() {213 long inUse = factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();214 return inUse / (float) maxSessionCount * 100f;215 }216 @ManagedAttribute(name = "RemoteNodeUri")217 public URI getExternalUri() {218 return this.getUri();219 }220 @ManagedAttribute(name = "GridUri")221 public URI getGridUri() {222 return this.gridUri;223 }224 @ManagedAttribute(name = "NodeId")225 public String getNodeId() {226 return getId().toString();227 }228 @Override229 public boolean isSupporting(Capabilities capabilities) {230 return factories.parallelStream().anyMatch(factory -> factory.test(capabilities));231 }232 @Override233 public Either<WebDriverException, CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {234 Require.nonNull("Session request", sessionRequest);235 try (Span span = tracer.getCurrentContext().createSpan("node.new_session")) {236 Map<String, EventAttributeValue> attributeMap = new HashMap<>();237 attributeMap238 .put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName()));239 LOG.fine("Creating new session using span: " + span);240 attributeMap.put("session.request.capabilities",241 EventAttribute.setValue(sessionRequest.getDesiredCapabilities().toString()));242 attributeMap.put("session.request.downstreamdialect",243 EventAttribute.setValue(sessionRequest.getDownstreamDialects().toString()));244 int currentSessionCount = getCurrentSessionCount();245 span.setAttribute("current.session.count", currentSessionCount);246 attributeMap.put("current.session.count", EventAttribute.setValue(currentSessionCount));247 if (getCurrentSessionCount() >= maxSessionCount) {248 span.setAttribute("error", true);249 span.setStatus(Status.RESOURCE_EXHAUSTED);250 attributeMap.put("max.session.count", EventAttribute.setValue(maxSessionCount));251 span.addEvent("Max session count reached", attributeMap);252 return Either.left(new RetrySessionRequestException("Max session count reached."));253 }254 if (isDraining()) {255 span.setStatus(Status.UNAVAILABLE.withDescription("The node is draining. Cannot accept new sessions."));256 return Either.left(257 new RetrySessionRequestException("The node is draining. Cannot accept new sessions."));258 }259 // Identify possible slots to use as quickly as possible to enable concurrent session starting260 SessionSlot slotToUse = null;261 synchronized(factories) {262 for (SessionSlot factory : factories) {263 if (!factory.isAvailable() || !factory.test(sessionRequest.getDesiredCapabilities())) {264 continue;265 }266 factory.reserve();267 slotToUse = factory;268 break;269 }270 }271 if (slotToUse == null) {272 span.setAttribute("error", true);273 span.setStatus(Status.NOT_FOUND);274 span.addEvent("No slot matched capabilities ", attributeMap);275 return Either.left(276 new RetrySessionRequestException("No slot matched the requested capabilities."));277 }278 Either<WebDriverException, ActiveSession> possibleSession = slotToUse.apply(sessionRequest);279 if (possibleSession.isRight()) {280 ActiveSession session = possibleSession.right();281 currentSessions.put(session.getId(), slotToUse);282 SessionId sessionId = session.getId();283 Capabilities caps = session.getCapabilities();284 SESSION_ID.accept(span, sessionId);285 CAPABILITIES.accept(span, caps);286 String downstream = session.getDownstreamDialect().toString();287 String upstream = session.getUpstreamDialect().toString();288 String sessionUri = session.getUri().toString();289 span.setAttribute(AttributeKey.DOWNSTREAM_DIALECT.getKey(), downstream);290 span.setAttribute(AttributeKey.UPSTREAM_DIALECT.getKey(), upstream);291 span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri);292 // The session we return has to look like it came from the node, since we might be dealing293 // with a webdriver implementation that only accepts connections from localhost294 boolean isSupportingCdp = slotToUse.isSupportingCdp() ||295 caps.getCapability("se:cdp") != null;296 Session externalSession = createExternalSession(session, externalUri, isSupportingCdp);297 return Either.right(new CreateSessionResponse(298 externalSession,299 getEncoder(session.getDownstreamDialect()).apply(externalSession)));300 } else {301 slotToUse.release();302 span.setAttribute("error", true);303 span.addEvent("Unable to create session with the driver", attributeMap);304 return Either.left(possibleSession.left());305 }306 }307 }308 @Override309 public boolean isSessionOwner(SessionId id) {310 Require.nonNull("Session ID", id);311 return currentSessions.getIfPresent(id) != null;312 }313 @Override314 public Session getSession(SessionId id) throws NoSuchSessionException {315 Require.nonNull("Session ID", id);316 SessionSlot slot = currentSessions.getIfPresent(id);317 if (slot == null) {318 throw new NoSuchSessionException("Cannot find session with id: " + id);319 }320 return createExternalSession(slot.getSession(), externalUri, slot.isSupportingCdp());321 }322 @Override323 public TemporaryFilesystem getTemporaryFilesystem(SessionId id) throws IOException {324 try {325 return tempFileSystems.get(id, () -> TemporaryFilesystem.getTmpFsBasedOn(326 TemporaryFilesystem.getDefaultTmpFS().createTempDir("session", id.toString())));327 } catch (ExecutionException e) {328 throw new IOException(e);329 }330 }331 @Override332 public HttpResponse executeWebDriverCommand(HttpRequest req) {333 // True enough to be good enough334 SessionId id = getSessionId(req.getUri()).map(SessionId::new)335 .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req));336 SessionSlot slot = currentSessions.getIfPresent(id);337 if (slot == null) {338 throw new NoSuchSessionException("Cannot find session with id: " + id);339 }340 ActiveSession activeSession = slot.getSession();341 if (activeSession.getClass().getName().contains("RelaySessionFactory")) {342 HttpResponse toReturn = slot.execute(req);343 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {344 stop(id);345 }346 return toReturn;347 }348 SauceDockerSession session = (SauceDockerSession) activeSession;349 SauceCommandInfo.Builder builder = new SauceCommandInfo.Builder();350 builder.setStartTime(Instant.now().toEpochMilli());351 HttpResponse toReturn = slot.execute(req);352 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {353 stop(id);354 builder.setScreenshotId(-1);355 } else {356 // Only taking screenshots after a url has been loaded357 if (!session.canTakeScreenshot() && req.getMethod() == POST358 && req.getUri().endsWith("/url")) {359 session.enableScreenshots();360 }361 int screenshotId = takeScreenshot(session, req, slot);362 builder.setScreenshotId(screenshotId);363 }364 Map<String, Object> parsedResponse =365 JSON.toType(new InputStreamReader(toReturn.getContent().get()), MAP_TYPE);366 builder.setRequest(getRequestContents(req))367 .setResult(parsedResponse)368 .setPath(req.getUri().replace(String.format("/session/%s", id), ""))369 .setHttpStatus(toReturn.getStatus())370 .setHttpMethod(req.getMethod().name())371 .setStatusCode(0);372 if (parsedResponse.containsKey("value") && parsedResponse.get("value") != null373 && parsedResponse.get("value").toString().contains("error")) {374 builder.setStatusCode(1);375 }376 builder.setEndTime(Instant.now().toEpochMilli());377 session.addSauceCommandInfo(builder.build());378 return toReturn;379 }380 @Override381 public HttpResponse uploadFile(HttpRequest req, SessionId id) {382 // When the session is running in a Docker container, the upload file command383 // needs to be forwarded to the container as well.384 SessionSlot slot = currentSessions.getIfPresent(id);385 if (slot != null && slot.getSession() instanceof SauceDockerSession) {386 return executeWebDriverCommand(req);387 }388 Map<String, Object> incoming = JSON.toType(string(req), Json.MAP_TYPE);389 File tempDir;390 try {391 TemporaryFilesystem tempfs = getTemporaryFilesystem(id);392 tempDir = tempfs.createTempDir("upload", "file");393 Zip.unzip((String) incoming.get("file"), tempDir);394 } catch (IOException e) {395 throw new UncheckedIOException(e);396 }397 // Select the first file398 File[] allFiles = tempDir.listFiles();399 if (allFiles == null) {400 throw new WebDriverException(401 String.format("Cannot access temporary directory for uploaded files %s", tempDir));402 }403 if (allFiles.length != 1) {404 throw new WebDriverException(405 String.format("Expected there to be only 1 file. There were: %s", allFiles.length));406 }407 ImmutableMap<String, Object> result = ImmutableMap.of(408 "value", allFiles[0].getAbsolutePath());409 return new HttpResponse().setContent(asJson(result));410 }411 @Override412 public void stop(SessionId id) throws NoSuchSessionException {413 Require.nonNull("Session ID", id);414 SessionSlot slot = currentSessions.getIfPresent(id);415 if (slot == null) {416 throw new NoSuchSessionException("Cannot find session with id: " + id);417 }418 currentSessions.invalidate(id);419 tempFileSystems.invalidate(id);420 }421 private void stopAllSessions() {422 if (currentSessions.size() > 0) {423 LOG.info("Trying to stop all running sessions before shutting down...");424 currentSessions.invalidateAll();425 }426 }427 private Session createExternalSession(ActiveSession other, URI externalUri, boolean isSupportingCdp) {428 Capabilities toUse = ImmutableCapabilities.copyOf(other.getCapabilities());429 // Rewrite the se:options if necessary to send the cdp url back430 if (isSupportingCdp) {431 String cdpPath = String.format("/session/%s/se/cdp", other.getId());432 toUse = new PersistentCapabilities(toUse).setCapability("se:cdp", rewrite(cdpPath));433 }434 return new Session(other.getId(), externalUri, other.getStereotype(), toUse, Instant.now());435 }436 private URI rewrite(String path) {437 try {438 String scheme = "https".equals(gridUri.getScheme()) ? "wss" : "ws";439 return new URI(440 scheme,441 gridUri.getUserInfo(),442 gridUri.getHost(),443 gridUri.getPort(),444 path,445 null,446 null);447 } catch (URISyntaxException e) {448 throw new RuntimeException(e);449 }450 }451 @Override452 public NodeStatus getStatus() {453 Set<Slot> slots = factories.stream()454 .map(slot -> {455 Instant lastStarted = Instant.EPOCH;456 Session session = null;457 if (!slot.isAvailable()) {458 ActiveSession activeSession = slot.getSession();459 if (activeSession != null) {460 lastStarted = activeSession.getStartTime();461 session = new Session(462 activeSession.getId(),463 activeSession.getUri(),464 slot.getStereotype(),465 activeSession.getCapabilities(),466 activeSession.getStartTime());467 }468 }469 return new Slot(470 new SlotId(getId(), slot.getId()),471 slot.getStereotype(),472 lastStarted,473 session);474 })475 .collect(toImmutableSet());476 return new NodeStatus(477 getId(),478 externalUri,479 maxSessionCount,480 slots,481 isDraining() ? DRAINING : UP,482 heartbeatPeriod,483 getNodeVersion(),484 getOsInfo());485 }486 @Override487 public HealthCheck getHealthCheck() {488 return healthCheck;489 }490 @Override491 public void drain() {492 bus.fire(new NodeDrainStarted(getId()));493 draining = true;494 int currentSessionCount = getCurrentSessionCount();495 if (currentSessionCount == 0) {496 LOG.info("Firing node drain complete message");497 bus.fire(new NodeDrainComplete(getId()));498 } else {499 pendingSessions.set(currentSessionCount);500 }501 }502 private Map<String, Object> toJson() {503 return ImmutableMap.of(504 "id", getId(),505 "uri", externalUri,506 "maxSessions", maxSessionCount,507 "draining", isDraining(),508 "capabilities", factories.stream()509 .map(SessionSlot::getStereotype)510 .collect(Collectors.toSet()));511 }512 public static class Builder {513 private final Tracer tracer;514 private final EventBus bus;515 private final URI uri;516 private final URI gridUri;517 private final Secret registrationSecret;518 private final ImmutableList.Builder<SessionSlot> factories;519 private int maxCount = Runtime.getRuntime().availableProcessors() * 5;520 private Ticker ticker = Ticker.systemTicker();521 private Duration sessionTimeout = Duration.ofMinutes(5);...
Source:LocalNode.java
...134 Require.nonNull("Registration secret", registrationSecret);135 this.healthCheck = healthCheck == null ?136 () -> new HealthCheck.Result(137 isDraining() ? DRAINING : UP,138 String.format("%s is %s", uri, isDraining() ? "draining" : "up")) :139 healthCheck;140 this.currentSessions = CacheBuilder.newBuilder()141 .expireAfterAccess(sessionTimeout)142 .ticker(ticker)143 .removalListener((RemovalListener<SessionId, SessionSlot>) notification -> {144 // If we were invoked explicitly, then return: we know what we're doing.145 if (!notification.wasEvicted()) {146 return;147 }148 killSession(notification.getValue());149 })150 .build();151 this.tempFileSystems = CacheBuilder.newBuilder()152 .expireAfterAccess(sessionTimeout)153 .ticker(ticker)154 .removalListener((RemovalListener<SessionId, TemporaryFilesystem>) notification -> {155 TemporaryFilesystem tempFS = notification.getValue();156 tempFS.deleteTemporaryFiles();157 tempFS.deleteBaseDir();158 })159 .build();160 this.regularly = new Regularly("Local Node: " + externalUri);161 regularly.submit(currentSessions::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));162 regularly.submit(tempFileSystems::cleanUp, Duration.ofSeconds(30), Duration.ofSeconds(30));163 bus.addListener(NodeAddedEvent.listener(nodeId -> {164 if (getId().equals(nodeId)) {165 // Lets avoid to create more than one "Regularly" when the Node registers again.166 if (!heartBeatStarted.getAndSet(true)) {167 regularly.submit(168 () -> bus.fire(new NodeHeartBeatEvent(getStatus())), heartbeatPeriod, heartbeatPeriod);169 }170 }171 }));172 bus.addListener(SessionClosedEvent.listener(id -> {173 try {174 this.stop(id);175 } catch (NoSuchSessionException ignore) {176 }177 if (this.isDraining()) {178 int done = pendingSessions.decrementAndGet();179 if (done <= 0) {180 LOG.info("Firing node drain complete message");181 bus.fire(new NodeDrainComplete(this.getId()));182 }183 }184 }));185 new JMXHelper().register(this);186 }187 public static Builder builder(188 Tracer tracer,189 EventBus bus,190 URI uri,191 URI gridUri,192 Secret registrationSecret) {193 return new Builder(tracer, bus, uri, gridUri, registrationSecret);194 }195 @Override196 public boolean isReady() {197 return bus.isReady();198 }199 @VisibleForTesting200 @ManagedAttribute(name = "CurrentSessions")201 public int getCurrentSessionCount() {202 // It seems wildly unlikely we'll overflow an int203 return Math.toIntExact(currentSessions.size());204 }205 @ManagedAttribute(name = "MaxSessions")206 public int getMaxSessionCount() {207 return maxSessionCount;208 }209 @ManagedAttribute(name = "Status")210 public Availability getAvailability() {211 return isDraining() ? DRAINING : UP;212 }213 @ManagedAttribute(name = "TotalSlots")214 public int getTotalSlots() {215 return factories.size();216 }217 @ManagedAttribute(name = "UsedSlots")218 public long getUsedSlots() {219 return factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();220 }221 @ManagedAttribute(name = "Load")222 public float getLoad() {223 long inUse = factories.stream().filter(sessionSlot -> !sessionSlot.isAvailable()).count();224 return inUse / (float) maxSessionCount * 100f;225 }226 @ManagedAttribute(name = "RemoteNodeUri")227 public URI getExternalUri() {228 return this.getUri();229 }230 @ManagedAttribute(name = "GridUri")231 public URI getGridUri() {232 return this.gridUri;233 }234 @ManagedAttribute(name = "NodeId")235 public String getNodeId() {236 return getId().toString();237 }238 @Override239 public boolean isSupporting(Capabilities capabilities) {240 return factories.parallelStream().anyMatch(factory -> factory.test(capabilities));241 }242 @Override243 public Either<WebDriverException, CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {244 Require.nonNull("Session request", sessionRequest);245 try (Span span = tracer.getCurrentContext().createSpan("node.new_session")) {246 Map<String, EventAttributeValue> attributeMap = new HashMap<>();247 attributeMap248 .put(AttributeKey.LOGGER_CLASS.getKey(), EventAttribute.setValue(getClass().getName()));249 attributeMap.put("session.request.capabilities",250 EventAttribute.setValue(sessionRequest.getDesiredCapabilities().toString()));251 attributeMap.put("session.request.downstreamdialect",252 EventAttribute.setValue(sessionRequest.getDownstreamDialects().toString()));253 int currentSessionCount = getCurrentSessionCount();254 span.setAttribute("current.session.count", currentSessionCount);255 attributeMap.put("current.session.count", EventAttribute.setValue(currentSessionCount));256 if (getCurrentSessionCount() >= maxSessionCount) {257 span.setAttribute("error", true);258 span.setStatus(Status.RESOURCE_EXHAUSTED);259 attributeMap.put("max.session.count", EventAttribute.setValue(maxSessionCount));260 span.addEvent("Max session count reached", attributeMap);261 return Either.left(new RetrySessionRequestException("Max session count reached."));262 }263 if (isDraining()) {264 span.setStatus(Status.UNAVAILABLE.withDescription("The node is draining. Cannot accept new sessions."));265 return Either.left(266 new RetrySessionRequestException("The node is draining. Cannot accept new sessions."));267 }268 // Identify possible slots to use as quickly as possible to enable concurrent session starting269 SessionSlot slotToUse = null;270 synchronized (factories) {271 for (SessionSlot factory : factories) {272 if (!factory.isAvailable() || !factory.test(sessionRequest.getDesiredCapabilities())) {273 continue;274 }275 factory.reserve();276 slotToUse = factory;277 break;278 }279 }280 if (slotToUse == null) {281 span.setAttribute("error", true);282 span.setStatus(Status.NOT_FOUND);283 span.addEvent("No slot matched the requested capabilities. ", attributeMap);284 return Either.left(285 new RetrySessionRequestException("No slot matched the requested capabilities."));286 }287 Either<WebDriverException, ActiveSession> possibleSession = slotToUse.apply(sessionRequest);288 if (possibleSession.isRight()) {289 ActiveSession session = possibleSession.right();290 currentSessions.put(session.getId(), slotToUse);291 SessionId sessionId = session.getId();292 Capabilities caps = session.getCapabilities();293 SESSION_ID.accept(span, sessionId);294 CAPABILITIES.accept(span, caps);295 String downstream = session.getDownstreamDialect().toString();296 String upstream = session.getUpstreamDialect().toString();297 String sessionUri = session.getUri().toString();298 span.setAttribute(AttributeKey.DOWNSTREAM_DIALECT.getKey(), downstream);299 span.setAttribute(AttributeKey.UPSTREAM_DIALECT.getKey(), upstream);300 span.setAttribute(AttributeKey.SESSION_URI.getKey(), sessionUri);301 // The session we return has to look like it came from the node, since we might be dealing302 // with a webdriver implementation that only accepts connections from localhost303 Session externalSession = createExternalSession(304 session,305 externalUri,306 slotToUse.isSupportingCdp() || caps.getCapability("se:cdp") != null);307 return Either.right(new CreateSessionResponse(308 externalSession,309 getEncoder(session.getDownstreamDialect()).apply(externalSession)));310 } else {311 slotToUse.release();312 span.setAttribute("error", true);313 span.addEvent("Unable to create session with the driver", attributeMap);314 return Either.left(possibleSession.left());315 }316 }317 }318 @Override319 public boolean isSessionOwner(SessionId id) {320 Require.nonNull("Session ID", id);321 return currentSessions.getIfPresent(id) != null;322 }323 @Override324 public Session getSession(SessionId id) throws NoSuchSessionException {325 Require.nonNull("Session ID", id);326 SessionSlot slot = currentSessions.getIfPresent(id);327 if (slot == null) {328 throw new NoSuchSessionException("Cannot find session with id: " + id);329 }330 return createExternalSession(slot.getSession(), externalUri, slot.isSupportingCdp());331 }332 @Override333 public TemporaryFilesystem getTemporaryFilesystem(SessionId id) throws IOException {334 try {335 return tempFileSystems.get(id, () -> TemporaryFilesystem.getTmpFsBasedOn(336 TemporaryFilesystem.getDefaultTmpFS().createTempDir("session", id.toString())));337 } catch (ExecutionException e) {338 throw new IOException(e);339 }340 }341 @Override342 public HttpResponse executeWebDriverCommand(HttpRequest req) {343 // True enough to be good enough344 SessionId id = getSessionId(req.getUri()).map(SessionId::new)345 .orElseThrow(() -> new NoSuchSessionException("Cannot find session: " + req));346 SessionSlot slot = currentSessions.getIfPresent(id);347 if (slot == null) {348 throw new NoSuchSessionException("Cannot find session with id: " + id);349 }350 HttpResponse toReturn = slot.execute(req);351 if (req.getMethod() == DELETE && req.getUri().equals("/session/" + id)) {352 stop(id);353 }354 return toReturn;355 }356 @Override357 public HttpResponse uploadFile(HttpRequest req, SessionId id) {358 Map<String, Object> incoming = JSON.toType(string(req), Json.MAP_TYPE);359 File tempDir;360 try {361 TemporaryFilesystem tempfs = getTemporaryFilesystem(id);362 tempDir = tempfs.createTempDir("upload", "file");363 Zip.unzip((String) incoming.get("file"), tempDir);364 } catch (IOException e) {365 throw new UncheckedIOException(e);366 }367 // Select the first file368 File[] allFiles = tempDir.listFiles();369 if (allFiles == null) {370 throw new WebDriverException(371 String.format("Cannot access temporary directory for uploaded files %s", tempDir));372 }373 if (allFiles.length != 1) {374 throw new WebDriverException(375 String.format("Expected there to be only 1 file. There were: %s", allFiles.length));376 }377 ImmutableMap<String, Object> result = ImmutableMap.of(378 "value", allFiles[0].getAbsolutePath());379 return new HttpResponse().setContent(asJson(result));380 }381 @Override382 public void stop(SessionId id) throws NoSuchSessionException {383 Require.nonNull("Session ID", id);384 SessionSlot slot = currentSessions.getIfPresent(id);385 if (slot == null) {386 throw new NoSuchSessionException("Cannot find session with id: " + id);387 }388 killSession(slot);389 tempFileSystems.invalidate(id);390 }391 private Session createExternalSession(ActiveSession other, URI externalUri, boolean isSupportingCdp) {392 Capabilities toUse = ImmutableCapabilities.copyOf(other.getCapabilities());393 // Rewrite the se:options if necessary to send the cdp url back394 if (isSupportingCdp) {395 String cdpPath = String.format("/session/%s/se/cdp", other.getId());396 toUse = new PersistentCapabilities(toUse).setCapability("se:cdp", rewrite(cdpPath));397 }398 return new Session(other.getId(), externalUri, other.getStereotype(), toUse, Instant.now());399 }400 private URI rewrite(String path) {401 try {402 return new URI(403 "ws",404 gridUri.getUserInfo(),405 gridUri.getHost(),406 gridUri.getPort(),407 path,408 null,409 null);410 } catch (URISyntaxException e) {411 throw new RuntimeException(e);412 }413 }414 private void killSession(SessionSlot slot) {415 currentSessions.invalidate(slot.getSession().getId());416 // Attempt to stop the session417 if (!slot.isAvailable()) {418 slot.stop();419 }420 }421 @Override422 public NodeStatus getStatus() {423 Set<Slot> slots = factories.stream()424 .map(slot -> {425 Instant lastStarted = Instant.EPOCH;426 Optional<Session> session = Optional.empty();427 if (!slot.isAvailable()) {428 ActiveSession activeSession = slot.getSession();429 if (activeSession != null) {430 lastStarted = activeSession.getStartTime();431 session = Optional.of(432 new Session(433 activeSession.getId(),434 activeSession.getUri(),435 slot.getStereotype(),436 activeSession.getCapabilities(),437 activeSession.getStartTime()));438 }439 }440 return new Slot(441 new SlotId(getId(), slot.getId()),442 slot.getStereotype(),443 lastStarted,444 session);445 })446 .collect(toImmutableSet());447 return new NodeStatus(448 getId(),449 externalUri,450 maxSessionCount,451 slots,452 isDraining() ? DRAINING : UP,453 heartbeatPeriod,454 getNodeVersion(),455 getOsInfo());456 }457 @Override458 public HealthCheck getHealthCheck() {459 return healthCheck;460 }461 @Override462 public void drain() {463 bus.fire(new NodeDrainStarted(getId()));464 draining = true;465 int currentSessionCount = getCurrentSessionCount();466 if (currentSessionCount == 0) {467 LOG.info("Firing node drain complete message");468 bus.fire(new NodeDrainComplete(getId()));469 } else {470 pendingSessions.set(currentSessionCount);471 }472 }473 private Map<String, Object> toJson() {474 return ImmutableMap.of(475 "id", getId(),476 "uri", externalUri,477 "maxSessions", maxSessionCount,478 "draining", isDraining(),479 "capabilities", factories.stream()480 .map(SessionSlot::getStereotype)481 .collect(Collectors.toSet()));482 }483 public static class Builder {484 private final Tracer tracer;485 private final EventBus bus;486 private final URI uri;487 private final URI gridUri;488 private final Secret registrationSecret;489 private final ImmutableList.Builder<SessionSlot> factories;490 private int maxCount = NodeOptions.DEFAULT_MAX_SESSIONS;491 private Ticker ticker = Ticker.systemTicker();492 private Duration sessionTimeout = Duration.ofSeconds(NodeOptions.DEFAULT_SESSION_TIMEOUT);...
Source:OneShotNode.java
...69import static org.openqa.selenium.grid.data.Availability.UP;70import static org.openqa.selenium.json.Json.MAP_TYPE;71import static org.openqa.selenium.remote.http.HttpMethod.DELETE;72/**73 * An implementation of {@link Node} that marks itself as draining immediately74 * after starting, and which then shuts down after usage. This will allow an75 * appropriately configured Kubernetes cluster to start a new node once the76 * session is finished.77 */78public class OneShotNode extends Node {79 private static final Logger LOG = Logger.getLogger(OneShotNode.class.getName());80 private static final Json JSON = new Json();81 private final EventBus events;82 private final WebDriverInfo driverInfo;83 private final Capabilities stereotype;84 private final URI gridUri;85 private final UUID slotId = UUID.randomUUID();86 private RemoteWebDriver driver;87 private SessionId sessionId;88 private HttpClient client;89 private Capabilities capabilities;90 private Instant sessionStart = Instant.EPOCH;91 private OneShotNode(92 Tracer tracer,93 EventBus events,94 Secret registrationSecret,95 NodeId id,96 URI uri,97 URI gridUri,98 Capabilities stereotype,99 WebDriverInfo driverInfo) {100 super(tracer, id, uri, registrationSecret);101 this.events = Require.nonNull("Event bus", events);102 this.gridUri = Require.nonNull("Public Grid URI", gridUri);103 this.stereotype = ImmutableCapabilities.copyOf(Require.nonNull("Stereotype", stereotype));104 this.driverInfo = Require.nonNull("Driver info", driverInfo);105 }106 public static Node create(Config config) {107 LoggingOptions loggingOptions = new LoggingOptions(config);108 EventBusOptions eventOptions = new EventBusOptions(config);109 BaseServerOptions serverOptions = new BaseServerOptions(config);110 NodeOptions nodeOptions = new NodeOptions(config);111 Map<String, Object> raw = new Json().toType(112 config.get("k8s", "stereotype")113 .orElseThrow(() -> new ConfigException("Unable to find node stereotype")),114 MAP_TYPE);115 Capabilities stereotype = new ImmutableCapabilities(raw);116 Optional<String> driverName = config.get("k8s", "driver_name").map(String::toLowerCase);117 // Find the webdriver info corresponding to the driver name118 WebDriverInfo driverInfo = StreamSupport.stream(ServiceLoader.load(WebDriverInfo.class).spliterator(), false)119 .filter(info -> info.isSupporting(stereotype))120 .filter(info -> driverName.map(name -> name.equals(info.getDisplayName().toLowerCase())).orElse(true))121 .findFirst()122 .orElseThrow(() -> new ConfigException(123 "Unable to find matching driver for %s and %s", stereotype, driverName.orElse("any driver")));124 LOG.info(String.format("Creating one-shot node for %s with stereotype %s", driverInfo, stereotype));125 LOG.info("Grid URI is: " + nodeOptions.getPublicGridUri());126 return new OneShotNode(127 loggingOptions.getTracer(),128 eventOptions.getEventBus(),129 serverOptions.getRegistrationSecret(),130 new NodeId(UUID.randomUUID()),131 serverOptions.getExternalUri(),132 nodeOptions.getPublicGridUri().orElseThrow(() -> new ConfigException("Unable to determine public grid address")),133 stereotype,134 driverInfo);135 }136 @Override137 public Optional<CreateSessionResponse> newSession(CreateSessionRequest sessionRequest) {138 if (driver != null) {139 throw new IllegalStateException("Only expected one session at a time");140 }141 Optional<WebDriver> driver = driverInfo.createDriver(sessionRequest.getCapabilities());142 if (!driver.isPresent()) {143 return Optional.empty();144 }145 if (!(driver.get() instanceof RemoteWebDriver)) {146 driver.get().quit();147 return Optional.empty();148 }149 this.driver = (RemoteWebDriver) driver.get();150 this.sessionId = this.driver.getSessionId();151 this.client = extractHttpClient(this.driver);152 this.capabilities = rewriteCapabilities(this.driver);153 this.sessionStart = Instant.now();154 LOG.info("Encoded response: " + JSON.toJson(ImmutableMap.of(155 "value", ImmutableMap.of(156 "sessionId", sessionId,157 "capabilities", capabilities))));158 events.fire(new NodeDrainStarted(getId()));159 return Optional.of(160 new CreateSessionResponse(161 getSession(sessionId),162 JSON.toJson(ImmutableMap.of(163 "value", ImmutableMap.of(164 "sessionId", sessionId,165 "capabilities", capabilities))).getBytes(UTF_8)));166 }167 private HttpClient extractHttpClient(RemoteWebDriver driver) {168 CommandExecutor executor = driver.getCommandExecutor();169 try {170 Field client = null;171 Class<?> current = executor.getClass();172 while (client == null && (current != null || Object.class.equals(current))) {173 client = findClientField(current);174 current = current.getSuperclass();175 }176 if (client == null) {177 throw new IllegalStateException("Unable to find client field in " + executor.getClass());178 }179 if (!HttpClient.class.isAssignableFrom(client.getType())) {180 throw new IllegalStateException("Client field is not assignable to http client");181 }182 client.setAccessible(true);183 return (HttpClient) client.get(executor);184 } catch (ReflectiveOperationException e) {185 throw new IllegalStateException(e);186 }187 }188 private Field findClientField(Class<?> clazz) {189 try {190 return clazz.getDeclaredField("client");191 } catch (NoSuchFieldException e) {192 return null;193 }194 }195 private Capabilities rewriteCapabilities(RemoteWebDriver driver) {196 // Rewrite the se:options if necessary197 Object rawSeleniumOptions = driver.getCapabilities().getCapability("se:options");198 if (rawSeleniumOptions == null || rawSeleniumOptions instanceof Map) {199 @SuppressWarnings("unchecked") Map<String, Object> original = (Map<String, Object>) rawSeleniumOptions;200 Map<String, Object> updated = new TreeMap<>(original == null ? new HashMap<>() : original);201 String cdpPath = String.format("/session/%s/se/cdp", driver.getSessionId());202 updated.put("cdp", rewrite(cdpPath));203 return new PersistentCapabilities(driver.getCapabilities()).setCapability("se:options", updated);204 }205 return ImmutableCapabilities.copyOf(driver.getCapabilities());206 }207 private URI rewrite(String path) {208 try {209 return new URI(210 gridUri.getScheme(),211 gridUri.getUserInfo(),212 gridUri.getHost(),213 gridUri.getPort(),214 path,215 null,216 null);217 } catch (URISyntaxException e) {218 throw new RuntimeException(e);219 }220 }221 @Override222 public HttpResponse executeWebDriverCommand(HttpRequest req) {223 LOG.info("Executing " + req);224 HttpResponse res = client.execute(req);225 if (DELETE.equals(req.getMethod()) && req.getUri().equals("/session/" + sessionId)) {226 // Ensure the response is sent before we viciously kill the node227 new Thread(228 () -> {229 try {230 Thread.sleep(500);231 } catch (InterruptedException e) {232 Thread.currentThread().interrupt();233 throw new RuntimeException(e);234 }235 LOG.info("Stopping session: " + sessionId);236 stop(sessionId);237 },238 "Node clean up: " + getId())239 .start();240 }241 return res;242 }243 @Override244 public Session getSession(SessionId id) throws NoSuchSessionException {245 if (!isSessionOwner(id)) {246 throw new NoSuchSessionException("Unable to find session with id: " + id);247 }248 return new Session(249 sessionId,250 getUri(),251 stereotype,252 capabilities,253 sessionStart); }254 @Override255 public HttpResponse uploadFile(HttpRequest req, SessionId id) {256 return null;257 }258 @Override259 public void stop(SessionId id) throws NoSuchSessionException {260 LOG.info("Stop has been called: " + id);261 Require.nonNull("Session ID", id);262 if (!isSessionOwner(id)) {263 throw new NoSuchSessionException("Unable to find session " + id);264 }265 LOG.info("Quitting session " + id);266 try {267 driver.quit();268 } catch (Exception e) {269 // It's possible that the driver has already quit.270 }271 events.fire(new SessionClosedEvent(id));272 LOG.info("Firing node drain complete message");273 events.fire(new NodeDrainComplete(getId()));274 }275 @Override276 public boolean isSessionOwner(SessionId id) {277 return driver != null && sessionId.equals(id);278 }279 @Override280 public boolean isSupporting(Capabilities capabilities) {281 return driverInfo.isSupporting(capabilities);282 }283 @Override284 public NodeStatus getStatus() {285 return new NodeStatus(286 getId(),287 getUri(),288 1,289 ImmutableSet.of(290 new Slot(291 new SlotId(getId(), slotId),292 stereotype,293 Instant.EPOCH,294 driver == null ?295 Optional.empty() :296 Optional.of(new Session(sessionId, getUri(), stereotype, capabilities, Instant.now())))),297 isDraining() ? DRAINING : UP);298 }299 @Override300 public void drain() {301 events.fire(new NodeDrainStarted(getId()));302 draining = true;303 }304 @Override305 public HealthCheck getHealthCheck() {306 return () -> new HealthCheck.Result(isDraining() ? DRAINING : UP, "Everything is fine");307 }308 @Override309 public boolean isReady() {310 return events.isReady();311 }312}...
Source:NodeServer.java
1// Licensed to the Software Freedom Conservancy (SFC) under one2// or more contributor license agreements. See the NOTICE file3// distributed with this work for additional information4// regarding copyright ownership. The SFC licenses this file5// to you under the Apache License, Version 2.0 (the6// "License"); you may not use this file except in compliance7// with the License. You may obtain a copy of the License at8//9// http://www.apache.org/licenses/LICENSE-2.010//11// Unless required by applicable law or agreed to in writing,12// software distributed under the License is distributed on an13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY14// KIND, either express or implied. See the License for the15// specific language governing permissions and limitations16// under the License.17package org.openqa.selenium.grid.node.httpd;18import com.google.auto.service.AutoService;19import com.google.common.collect.ImmutableSet;20import com.google.common.net.MediaType;21import net.jodah.failsafe.Failsafe;22import net.jodah.failsafe.RetryPolicy;23import org.openqa.selenium.BuildInfo;24import org.openqa.selenium.cli.CliCommand;25import org.openqa.selenium.events.EventBus;26import org.openqa.selenium.grid.TemplateGridCommand;27import org.openqa.selenium.grid.config.Config;28import org.openqa.selenium.grid.config.Role;29import org.openqa.selenium.grid.data.NodeAddedEvent;30import org.openqa.selenium.grid.data.NodeDrainComplete;31import org.openqa.selenium.grid.data.NodeStatusEvent;32import org.openqa.selenium.grid.log.LoggingOptions;33import org.openqa.selenium.grid.node.HealthCheck;34import org.openqa.selenium.grid.node.Node;35import org.openqa.selenium.grid.node.ProxyNodeCdp;36import org.openqa.selenium.grid.node.config.NodeOptions;37import org.openqa.selenium.grid.server.BaseServerOptions;38import org.openqa.selenium.grid.server.EventBusOptions;39import org.openqa.selenium.grid.server.NetworkOptions;40import org.openqa.selenium.grid.server.Server;41import org.openqa.selenium.netty.server.NettyServer;42import org.openqa.selenium.remote.http.Contents;43import org.openqa.selenium.remote.http.HttpClient;44import org.openqa.selenium.remote.http.HttpHandler;45import org.openqa.selenium.remote.http.HttpResponse;46import org.openqa.selenium.remote.http.Route;47import org.openqa.selenium.remote.tracing.Tracer;48import java.time.Duration;49import java.time.temporal.ChronoUnit;50import java.util.Collections;51import java.util.Set;52import java.util.concurrent.Executors;53import java.util.logging.Logger;54import static java.net.HttpURLConnection.HTTP_INTERNAL_ERROR;55import static java.net.HttpURLConnection.HTTP_NO_CONTENT;56import static org.openqa.selenium.grid.config.StandardGridRoles.EVENT_BUS_ROLE;57import static org.openqa.selenium.grid.config.StandardGridRoles.HTTPD_ROLE;58import static org.openqa.selenium.grid.config.StandardGridRoles.NODE_ROLE;59import static org.openqa.selenium.grid.data.Availability.DOWN;60import static org.openqa.selenium.remote.http.Route.get;61@AutoService(CliCommand.class)62public class NodeServer extends TemplateGridCommand {63 private static final Logger LOG = Logger.getLogger(NodeServer.class.getName());64 @Override65 public String getName() {66 return "node";67 }68 @Override69 public String getDescription() {70 return "Adds this server as a node in the selenium grid.";71 }72 @Override73 public Set<Role> getConfigurableRoles() {74 return ImmutableSet.of(EVENT_BUS_ROLE, HTTPD_ROLE, NODE_ROLE);75 }76 @Override77 public Set<Object> getFlagObjects() {78 return Collections.emptySet();79 }80 @Override81 protected String getSystemPropertiesConfigPrefix() {82 return "node";83 }84 @Override85 protected Config getDefaultConfig() {86 return new DefaultNodeConfig();87 }88 @Override89 protected void execute(Config config) {90 LoggingOptions loggingOptions = new LoggingOptions(config);91 Tracer tracer = loggingOptions.getTracer();92 EventBusOptions events = new EventBusOptions(config);93 EventBus bus = events.getEventBus();94 NetworkOptions networkOptions = new NetworkOptions(config);95 HttpClient.Factory clientFactory = networkOptions.getHttpClientFactory(tracer);96 BaseServerOptions serverOptions = new BaseServerOptions(config);97 LOG.info("Reporting self as: " + serverOptions.getExternalUri());98 NodeOptions nodeOptions = new NodeOptions(config);99 Node node = nodeOptions.getNode();100 HttpHandler readinessCheck = req -> {101 if (node.getStatus().hasCapacity()) {102 return new HttpResponse()103 .setStatus(HTTP_NO_CONTENT);104 }105 return new HttpResponse()106 .setStatus(HTTP_INTERNAL_ERROR)107 .setHeader("Content-Type", MediaType.PLAIN_TEXT_UTF_8.toString())108 .setContent(Contents.utf8String("No capacity available"));109 };110 bus.addListener(NodeAddedEvent.listener(nodeId -> {111 if (node.getId().equals(nodeId)) {112 LOG.info("Node has been added");113 }114 }));115 bus.addListener(NodeDrainComplete.listener(nodeId -> {116 if (!node.getId().equals(nodeId)) {117 return;118 }119 // Wait a beat before shutting down so the final response from the120 // node can escape.121 new Thread(122 () -> {123 try {124 Thread.sleep(1000);125 } catch (InterruptedException e) {126 // Swallow, the next thing we're doing is shutting down127 }128 LOG.info("Shutting down");129 System.exit(0);130 },131 "Node shutdown: " + nodeId)132 .start();133 }));134 Route httpHandler = Route.combine(135 node,136 get("/readyz").to(() -> readinessCheck));137 Server<?> server = new NettyServer(serverOptions, httpHandler, new ProxyNodeCdp(clientFactory, node));138 server.start();139 BuildInfo info = new BuildInfo();140 LOG.info(String.format(141 "Started Selenium node %s (revision %s): %s",142 info.getReleaseLabel(),143 info.getBuildRevision(),144 server.getUrl()));145 // Unlimited attempts, initial 5 seconds interval, backoff rate of 1.0005, max interval of 5 minutes146 RetryPolicy<Object> registrationPolicy = new RetryPolicy<>()147 .withMaxAttempts(-1)148 .handleResultIf(result -> true)149 .withBackoff(Duration.ofSeconds(5).getSeconds(), Duration.ofMinutes(5).getSeconds(), ChronoUnit.SECONDS, 1.0005);150 LOG.info("Starting registration process for node id " + node.getId());151 Executors.newSingleThreadExecutor().submit(() -> {152 Failsafe.with(registrationPolicy).run(153 () -> {154 LOG.fine("Sending registration event");155 HealthCheck.Result check = node.getHealthCheck().check();156 if (DOWN.equals(check.getAvailability())) {157 LOG.severe("Node is not alive: " + check.getMessage());158 // Throw an exception to force another check sooner.159 throw new UnsupportedOperationException("Node cannot be registered");160 }161 bus.fire(new NodeStatusEvent(node.getStatus()));162 }163 );164 });165 }166}...
drain
Using AI Code Generation
1public void drain() {2 this.sessions.forEach((id, session) -> {3 try {4 session.stop();5 } catch (Exception e) {6 LOG.log(Level.SEVERE, "Error stopping session " + id, e);7 }8 });9}10public void stop() {11 this.events.fire(new SessionClosedEvent(this.id));12 this.events.fire(new SessionDeletedEvent(this.id));13 this.events.fire(new SessionTerminatedEvent(this.id));14}15public void fire(SessionEvent event) {16 this.listeners.forEach(listener -> listener.onEvent(event));17}18public interface SessionListener {19 void onEvent(SessionEvent event);20}21public abstract class SessionEvent {22 private final SessionId id;23 public SessionEvent(SessionId id) {24 this.id = id;25 }26 public SessionId getId() {27 return this.id;28 }29}30public class SessionClosedEvent extends SessionEvent {31 public SessionClosedEvent(SessionId id) {32 super(id);33 }34}35public class SessionDeletedEvent extends SessionEvent {36 public SessionDeletedEvent(SessionId id) {37 super(id);38 }39}40public class SessionTerminatedEvent extends SessionEvent {41 public SessionTerminatedEvent(SessionId id) {42 super(id);43 }44}45public class SessionId {46 private final String id;47 public SessionId(String id) {48 this.id = id;49 }50 public String toString() {51 return this.id;52 }53}54public class NodeEvents {55 private final List<NodeListener> listeners;56 public NodeEvents() {57 this.listeners = new CopyOnWriteArrayList<>();58 }59 public void add(NodeListener listener) {60 this.listeners.add(listener);61 }62 public void remove(NodeListener listener) {63 this.listeners.remove(listener);64 }65 public void fire(NodeEvent event) {66 this.listeners.forEach(listener -> listener.onEvent(event));67 }68}69public interface NodeListener {70 void onEvent(NodeEvent event);71}72public abstract class NodeEvent {73 private final NodeId id;74 public NodeEvent(NodeId id) {75 this.id = id;
drain
Using AI Code Generation
1 private void drain() {2 ExecutorService executor = Executors.newFixedThreadPool(1);3 Future<?> future = executor.submit(() -> {4 Thread.sleep(5000);5 return "Callable task completed.";6 });7 executor.shutdown();8 while (!future.isDone()) {9 Thread.sleep(1000);10 logger.info("Waiting for the callable task to complete...");11 }12 logger.info("Callable task completed.");13 }14 private void drain() {15 ExecutorService executor = Executors.newFixedThreadPool(1);16 Future<?> future = executor.submit(() -> {17 Thread.sleep(5000);18 return "Callable task completed.";19 });20 executor.shutdown();21 while (!future.isDone()) {22 Thread.sleep(1000);23 logger.info("Waiting for the callable task to complete...");24 }25 logger.info("Callable task completed.");26 }27 private void drain() {28 ExecutorService executor = Executors.newFixedThreadPool(1);29 Future<?> future = executor.submit(() -> {30 Thread.sleep(5000);31 return "Callable task completed.";32 });33 executor.shutdown();34 while (!future.isDone()) {35 Thread.sleep(1000);
drain
Using AI Code Generation
1import org.openqa.selenium.grid.node.Node;2node.drain();3import org.openqa.selenium.grid.node.Node;4node.undrain();5import org.openqa.selenium.grid.node.Node;6node.drain();7import org.openqa.selenium.grid.node.Node;8node.undrain();9import org.openqa.selenium.grid.node.Node;10node.drain();11import org.openqa.selenium.grid.node.Node;12node.undrain();13import org.openqa.selenium.grid.node.Node;14node.drain();15import org.openqa.selenium.grid.node.Node;16node.undrain();17import org.openqa.selenium.grid.node.Node;
drain
Using AI Code Generation
1public void drain() throws IOException2package org.openqa.selenium.grid.node; 3import java.io.IOException; 4public class Node { 5public void drain() throws IOException { 6} 7}8public void drain() throws IOException9package org.openqa.selenium.grid.node; 10import java.io.IOException; 11public class Node { 12public void drain() throws IOException { 13} 14}15public NodeId get()16package org.openqa.selenium.grid.node; 17public class Node { 18public NodeId get() { 19return null; 20} 21}
LambdaTest’s Selenium 4 tutorial is covering every aspects of Selenium 4 testing with examples and best practices. Here you will learn basics, such as how to upgrade from Selenium 3 to Selenium 4, to some advanced concepts, such as Relative locators and Selenium Grid 4 for Distributed testing. Also will learn new features of Selenium 4, such as capturing screenshots of specific elements, opening a new tab or window on the browser, and new protocol adoptions.
Upgrading From Selenium 3 To Selenium 4?: In this chapter, learn in detail how to update Selenium 3 to Selenium 4 for Java binding. Also, learn how to upgrade while using different build tools such as Maven or Gradle and get comprehensive guidance for upgrading Selenium.
What’s New In Selenium 4 & What’s Being Deprecated? : Get all information about new implementations in Selenium 4, such as W3S protocol adaption, Optimized Selenium Grid, and Enhanced Selenium IDE. Also, learn what is deprecated for Selenium 4, such as DesiredCapabilites and FindsBy methods, etc.
Selenium 4 With Python: Selenium supports all major languages, such as Python, C#, Ruby, and JavaScript. In this chapter, learn how to install Selenium 4 for Python and the features of Python in Selenium 4, such as Relative locators, Browser manipulation, and Chrom DevTool protocol.
Selenium 4 Is Now W3C Compliant: JSON Wireframe protocol is retiring from Selenium 4, and they are adopting W3C protocol to learn in detail about the advantages and impact of these changes.
How To Use Selenium 4 Relative Locator? : Selenium 4 came with new features such as Relative Locators that allow constructing locators with reference and easily located constructors nearby. Get to know its different use cases with examples.
Selenium Grid 4 Tutorial For Distributed Testing: Selenium Grid 4 allows you to perform tests over different browsers, OS, and device combinations. It also enables parallel execution browser testing, reads up on various features of Selenium Grid 4 and how to download it, and runs a test on Selenium Grid 4 with best practices.
Selenium Video Tutorials: Binge on video tutorials on Selenium by industry experts to get step-by-step direction from automating basic to complex test scenarios with Selenium.
LambdaTest also provides certification for Selenium testing to accelerate your career in Selenium automation testing.
Get 100 minutes of automation test minutes FREE!!