Phần tử ngẫu nhiên không còn được gắn vào DOM DOM StaleEuityReferenceException


143

Tôi hy vọng đó chỉ là tôi, nhưng Selenium Webdo dường như là một cơn ác mộng hoàn toàn. Trình duyệt web Chrome hiện không thể sử dụng được và các trình điều khiển khác khá không đáng tin, hoặc có vẻ như vậy. Tôi đang chiến đấu với nhiều vấn đề, nhưng đây là một vấn đề.

Ngẫu nhiên, các bài kiểm tra của tôi sẽ thất bại với một

"org.openqa.selenium.StaleElementReferenceException: Element is no longer attached 
to the DOM    
System info: os.name: 'Windows 7', os.arch: 'amd64',
 os.version: '6.1', java.version: '1.6.0_23'"

Tôi đang sử dụng phiên bản webdo 2.0b3. Tôi đã thấy điều này xảy ra với trình điều khiển FF và IE. Cách duy nhất tôi có thể ngăn chặn điều này là thêm một cuộc gọi thực tế Thread.sleeptrước khi ngoại lệ xảy ra. Đó là một cách giải quyết kém, vì vậy tôi hy vọng ai đó có thể chỉ ra một lỗi về phía tôi sẽ làm cho tất cả điều này tốt hơn.


26
Hy vọng rằng 17 nghìn lượt xem cho thấy đó không chỉ là bạn;) Đây phải là ngoại lệ Selenium khó chịu nhất ngoài kia.
Đánh dấu Mayo

4
48k ngay! Tôi có cùng một vấn đề ...
Gal

3
Tôi thấy rằng Selenium là rác nguyên chất và hoàn chỉnh ....
C Johnson

4
60k, vẫn là một vấn đề :)
Pieter De Bie

trong trường hợp của tôi đó là vì làmfrom selenium.common.exceptions import NoSuchElementException
Cpt. Senkfuss

Câu trả lời:


119

Có, nếu bạn gặp vấn đề với StaleEuityReferenceExceptions thì đó là do các bài kiểm tra của bạn được viết kém. Đó là một điều kiện chủng tộc. Hãy xem xét kịch bản sau đây:

WebElement element = driver.findElement(By.id("foo"));
// DOM changes - page is refreshed, or element is removed and re-added
element.click();

Bây giờ tại điểm bạn đang nhấp vào phần tử, tham chiếu phần tử không còn hợp lệ. WebDriver gần như không thể đoán được tất cả các trường hợp có thể xảy ra - vì vậy, nó sẽ giơ tay và kiểm soát bạn, người mà tác giả thử nghiệm / ứng dụng nên biết chính xác điều gì có thể xảy ra. Những gì bạn muốn làm là chờ đợi một cách rõ ràng cho đến khi DOM ở trạng thái mà bạn biết mọi thứ sẽ không thay đổi. Ví dụ: sử dụng WebDriverWait để chờ một phần tử cụ thể tồn tại:

// times out after 5 seconds
WebDriverWait wait = new WebDriverWait(driver, 5);

// while the following loop runs, the DOM changes - 
// page is refreshed, or element is removed and re-added
wait.until(presenceOfElementLocated(By.id("container-element")));        

// now we're good - let's click the element
driver.findElement(By.id("foo")).click();

Phương thức PresentOfEuityLocated () sẽ trông giống như thế này:

private static Function<WebDriver,WebElement> presenceOfElementLocated(final By locator) {
    return new Function<WebDriver, WebElement>() {
        @Override
        public WebElement apply(WebDriver driver) {
            return driver.findElement(locator);
        }
    };
}

Bạn hoàn toàn đúng về trình điều khiển Chrome hiện tại khá không ổn định và bạn sẽ rất vui khi biết rằng thân cây Selenium có trình điều khiển Chrome được viết lại, trong đó phần lớn việc triển khai được thực hiện bởi các nhà phát triển Chromium như một phần của cây.

Tái bút Ngoài ra, thay vì chờ đợi rõ ràng như trong ví dụ trên, bạn có thể kích hoạt chờ đợi ngầm - theo cách này, WebDriver sẽ luôn lặp lại cho đến khi hết thời gian chờ chỉ định để phần tử xuất hiện:

driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS)

Theo kinh nghiệm của tôi, chờ đợi rõ ràng luôn đáng tin cậy hơn.


2
Tôi có đúng không khi nói rằng không còn có thể đọc các phần tử thành các biến và sử dụng lại chúng? Bởi vì tôi có một DSL DSL khô và năng động rất lớn, phụ thuộc vào việc chuyển các phần tử và tôi đang cố gắng chuyển sang webdo, nhưng tôi gặp vấn đề tương tự. Về cơ bản, tôi sẽ phải thêm mã để đọc lại tất cả các thành phần trong mô-đun cho mỗi bước kiểm tra làm thay đổi DOM ...
kinofrost

Chào. Tôi có thể hỏi loại Hàm nào trong ví dụ này không? Tôi dường như không thể tìm thấy nó .... CẢM ƠN!
Hannibal

1
@Hannibal : com.google.common.base.Function<F, T>, được cung cấp bởi ổi .
Stephan202

@jarib, tôi đang đối mặt với vấn đề này một năm kể từ khi giải pháp của bạn. vấn đề là tôi đang viết các tập lệnh của mình bằng ruby ​​và không có chức năng nào có tên là 'PresentOfEuityLocated' hoặc bất cứ điều gì tương tự. Có đề xuất gì không?
Amey

56
@jarib Tôi không đồng ý điều này là do thử nghiệm được thiết kế kém. Bởi vì ngay cả sau khi phần tử xuất hiện sau lệnh gọi AJAX, có thể mã jQuery vẫn đang chạy có thể gây ra StaleEuityReferenceException. Và bạn không thể làm gì ngoài việc thêm chờ đợi rõ ràng, điều này có vẻ không hay lắm. Tôi nghĩ rằng đây là một lỗ hổng thiết kế trong WebDriver
munch

10

Tôi đã có thể sử dụng một phương pháp như thế này với một số thành công:

WebElement getStaleElemById(String id) {
    try {
        return driver.findElement(By.id(id));
    } catch (StaleElementReferenceException e) {
        System.out.println("Attempting to recover from StaleElementReferenceException ...");
        return getStaleElemById(id);
    }
}

Vâng, nó chỉ tiếp tục bỏ phiếu cho phần tử cho đến khi nó không còn được coi là cũ (tươi?). Không thực sự đi đến gốc rễ của vấn đề, nhưng tôi đã thấy rằng WebDriver có thể khá kén chọn về việc ném ngoại lệ này - đôi khi tôi hiểu nó, và đôi khi tôi không. Hoặc có thể là DOM thực sự đang thay đổi.

Vì vậy, tôi không hoàn toàn đồng ý với câu trả lời ở trên rằng điều này nhất thiết chỉ ra một bài kiểm tra viết kém. Tôi đã có nó trên các trang mới mà tôi chưa tương tác với bất kỳ cách nào. Tôi nghĩ rằng có một số điểm yếu trong cách DOM được thể hiện hoặc trong những gì WebDriver coi là cũ.


7
Bạn có một lỗi trong mã này, bạn không nên gọi phương thức theo cách đệ quy mà không có một số loại giới hạn hoặc bạn sẽ thổi tung ngăn xếp của mình.
Harry

2
Tôi nghĩ rằng tốt hơn là thêm một bộ đếm hoặc một cái gì đó, vì vậy khi chúng tôi nhận được lỗi liên tục, chúng tôi thực sự có thể ném lỗi.
Mặt

Tôi đồng ý rằng đó không phải là kết quả của các bài kiểm tra viết kém. Có xu hướng Selenium thực hiện điều này trên các trang web hiện đại, ngay cả đối với các bài kiểm tra viết tốt nhất - có thể là do các trang web liên tục làm mới các yếu tố của chúng thông qua các ràng buộc hai chiều phổ biến trong các khung ứng dụng web phản ứng, ngay cả khi không có thay đổi nào những yếu tố cần phải được thực hiện. Một phương pháp như thế này phải là một phần của mọi khung Selenium kiểm tra một ứng dụng web hiện đại.
emery

10

Đôi khi tôi gặp lỗi này khi cập nhật AJAX ở giữa chừng. Capybara có vẻ khá thông minh trong việc chờ đợi các thay đổi DOM (xem Tại sao Wait_until bị xóa khỏi Capybara ), nhưng thời gian chờ mặc định là 2 giây chỉ đơn giản là không đủ trong trường hợp của tôi. Đã thay đổi trong _spec_helper.rb_ với vd

Capybara.default_max_wait_time = 5

2
Điều này cũng đã khắc phục vấn đề của tôi: Tôi đã nhận được StaleEuityReferenceError và tăng Capybara.default_max_wait_time đã giải quyết vấn đề.
brendan

1

Tôi đã phải đối mặt với cùng một vấn đề ngày hôm nay và tạo ra một lớp bao bọc, nó kiểm tra trước mọi phương thức nếu tham chiếu phần tử vẫn còn hiệu lực. Giải pháp của tôi để lấy lại phần tử khá đơn giản vì vậy tôi nghĩ tôi chỉ muốn chia sẻ nó.

private void setElementLocator()
{
    this.locatorVariable = "selenium_" + DateTimeMethods.GetTime().ToString();
    ((IJavaScriptExecutor)this.driver).ExecuteScript(locatorVariable + " = arguments[0];", this.element);
}

private void RetrieveElement()
{
    this.element = (IWebElement)((IJavaScriptExecutor)this.driver).ExecuteScript("return " + locatorVariable);
}

Bạn thấy tôi "định vị" hoặc đúng hơn là lưu phần tử trong biến js toàn cầu và truy xuất phần tử nếu cần. Nếu trang được tải lại, tham chiếu này sẽ không hoạt động nữa. Nhưng miễn là chỉ có những thay đổi được thực hiện để diệt vong thì tham chiếu vẫn còn. Và điều đó sẽ làm công việc trong hầu hết các trường hợp.

Ngoài ra, nó tránh tìm kiếm lại các yếu tố.

John


1

Tôi đã có cùng một vấn đề và tôi đã gây ra bởi một phiên bản selen cũ. Tôi không thể cập nhật lên phiên bản mới hơn do môi trường phát triển. Vấn đề được gây ra bởi HTMLUnitWebEuity.switchF FocusTo This IfNeeded (). Khi bạn điều hướng đến một trang mới, có thể xảy ra yếu tố bạn nhấp vào trang cũ là oldActiveElement(xem bên dưới). Selenium cố gắng lấy bối cảnh từ yếu tố cũ và thất bại. Đó là lý do tại sao họ xây dựng một thử bắt trong các phiên bản tương lai.

Mã từ phiên bản trình điều khiển selenium-htmlunit <2.23.0:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
    if (jsEnabled &&
        !oldActiveEqualsCurrent &&
        !isBody) {
      oldActiveElement.element.blur();
      element.focus();
    }
}

Mã từ phiên bản trình điều khiển selenium-htmlunit> = 2.23.0:

private void switchFocusToThisIfNeeded() {
    HtmlUnitWebElement oldActiveElement =
        ((HtmlUnitWebElement)parent.switchTo().activeElement());

    boolean jsEnabled = parent.isJavascriptEnabled();
    boolean oldActiveEqualsCurrent = oldActiveElement.equals(this);
    try {
        boolean isBody = oldActiveElement.getTagName().toLowerCase().equals("body");
        if (jsEnabled &&
            !oldActiveEqualsCurrent &&
            !isBody) {
        oldActiveElement.element.blur();
        }
    } catch (StaleElementReferenceException ex) {
      // old element has gone, do nothing
    }
    element.focus();
}

Nếu không cập nhật lên 2.23.0 hoặc mới hơn, bạn chỉ có thể đưa ra bất kỳ yếu tố nào trên tiêu điểm của trang. Tôi chỉ sử dụng element.click()ví dụ.


1
Wow ... Đây là một phát hiện thực sự mơ hồ, công việc tuyệt vời .. Bây giờ tôi đang tự hỏi liệu các trình điều khiển khác (ví dụ: chromedriver) cũng có vấn đề tương tự
kevlarr

0

Chỉ xảy ra với tôi khi cố gắng gửi_key vào hộp nhập tìm kiếm - có chức năng tự động tùy thuộc vào nội dung bạn nhập. Như Eero đã đề cập, điều này có thể xảy ra nếu phần tử của bạn cập nhật một số Ajax trong khi bạn đang nhập văn bản của mình bên trong phần tử đầu vào . Giải pháp là gửi một ký tự một lần và tìm kiếm lại phần tử đầu vào . (Ví dụ: trong ruby ​​hiển thị bên dưới)

def send_keys_eachchar(webdriver, elem_locator, text_to_send)
  text_to_send.each_char do |char|
    input_elem = webdriver.find_element(elem_locator)
    input_elem.send_keys(char)
  end
end

0

Để thêm vào câu trả lời của @ jarib, tôi đã thực hiện một số phương pháp mở rộng giúp loại bỏ điều kiện cuộc đua.

Đây là thiết lập của tôi:

Tôi có một lớp được gọi là "Driver.cs". Nó chứa một lớp tĩnh chứa đầy các phương thức mở rộng cho trình điều khiển và các hàm tĩnh hữu ích khác.

Đối với các phần tử tôi thường cần truy xuất, tôi tạo một phương thức mở rộng như sau:

public static IWebElement SpecificElementToGet(this IWebDriver driver) {
    return driver.FindElement(By.SomeSelector("SelectorText"));
}

Điều này cho phép bạn truy xuất phần tử đó từ bất kỳ lớp kiểm tra nào với mã:

driver.SpecificElementToGet();

Bây giờ, nếu điều này dẫn đến một StaleElementReferenceException, tôi có phương thức tĩnh sau trong lớp trình điều khiển của mình:

public static void WaitForDisplayed(Func<IWebElement> getWebElement, int timeOut)
{
    for (int second = 0; ; second++)
    {
        if (second >= timeOut) Assert.Fail("timeout");
        try
        {
            if (getWebElement().Displayed) break;
        }
        catch (Exception)
        { }
        Thread.Sleep(1000);
    }
}

Tham số đầu tiên của hàm này là bất kỳ hàm nào trả về đối tượng IWebEuity. Tham số thứ hai là thời gian chờ tính bằng giây (mã cho thời gian chờ đã được sao chép từ Selenium IDE cho FireFox). Mã có thể được sử dụng để tránh ngoại lệ phần tử cũ theo cách sau:

MyTestDriver.WaitForDisplayed(driver.SpecificElementToGet,5);

Đoạn mã trên sẽ gọi driver.SpecificElementToGet().Displayedcho đến khi driver.SpecificElementToGet()ném không có ngoại lệ và .Displayedđánh giá truevà 5 giây chưa trôi qua. Sau 5 giây, bài kiểm tra sẽ thất bại.

Mặt khác, để chờ một phần tử không xuất hiện, bạn có thể sử dụng chức năng sau theo cách tương tự:

public static void WaitForNotPresent(Func<IWebElement> getWebElement, int timeOut) {
    for (int second = 0;; second++) {
        if (second >= timeOut) Assert.Fail("timeout");
            try
            {
                if (!getWebElement().Displayed) break;
            }
            catch (ElementNotVisibleException) { break; }
            catch (NoSuchElementException) { break; }
            catch (StaleElementReferenceException) { break; }
            catch (Exception)
            { }
            Thread.Sleep(1000);
        }
}

0

Tôi nghĩ rằng tôi đã tìm thấy cách tiếp cận thuận tiện để xử lý StaleEuityReferenceException. Thông thường, bạn phải viết các hàm bao cho mọi phương thức WebE bổ sung để thử lại các hành động, điều này gây bực bội và lãng phí rất nhiều thời gian.

Thêm mã này

webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));

if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
    webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
}

trước mỗi hành động WebE bổ sung có thể làm tăng tính ổn định của các thử nghiệm của bạn nhưng thỉnh thoảng bạn vẫn có thể nhận được StaleEuityReferenceException.

Vì vậy, đây là những gì tôi đã đưa ra (sử dụng AspectJ):

package path.to.your.aspects;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.StaleElementReferenceException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.pagefactory.DefaultElementLocator;
import org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@Aspect
public class WebElementAspect {
    private static final Logger LOG = LogManager.getLogger(WebElementAspect.class);
    /**
     * Get your WebDriver instance from some kind of manager
     */
    private WebDriver webDriver = DriverManager.getWebDriver();
    private WebDriverWait webDriverWait = new WebDriverWait(webDriver, 10);

    /**
     * This will intercept execution of all methods from WebElement interface
     */
    @Pointcut("execution(* org.openqa.selenium.WebElement.*(..))")
    public void webElementMethods() {}

    /**
     * @Around annotation means that you can insert additional logic
     * before and after execution of the method
     */
    @Around("webElementMethods()")
    public Object webElementHandler(ProceedingJoinPoint joinPoint) throws Throwable {
        /**
         * Waiting until JavaScript and jQuery complete their stuff
         */
        waitUntilPageIsLoaded();

        /**
         * Getting WebElement instance, method, arguments
         */
        WebElement webElement = (WebElement) joinPoint.getThis();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object[] args = joinPoint.getArgs();

        /**
         * Do some logging if you feel like it
         */
        String methodName = method.getName();

        if (methodName.contains("click")) {
            LOG.info("Clicking on " + getBy(webElement));
        } else if (methodName.contains("select")) {
            LOG.info("Selecting from " + getBy(webElement));
        } else if (methodName.contains("sendKeys")) {
            LOG.info("Entering " + args[0].toString() + " into " + getBy(webElement));
        }

        try {
            /**
             * Executing WebElement method
             */
            return joinPoint.proceed();
        } catch (StaleElementReferenceException ex) {
            LOG.debug("Intercepted StaleElementReferenceException");

            /**
             * Refreshing WebElement
             * You can use implementation from this blog
             * http://www.sahajamit.com/post/mystery-of-stale-element-reference-exception/
             * but remove staleness check in the beginning (if(!isElementStale(elem))), because we already caught exception
             * and it will result in an endless loop
             */
            webElement = StaleElementUtil.refreshElement(webElement);

            /**
             * Executing method once again on the refreshed WebElement and returning result
             */
            return method.invoke(webElement, args);
        }
    }

    private void waitUntilPageIsLoaded() {
        webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return document.readyState").equals("complete")));

        if ((Boolean) ((JavascriptExecutor) webDriver).executeScript("return window.jQuery != undefined")) {
            webDriverWait.until((webDriver1) -> (((JavascriptExecutor) webDriver).executeScript("return jQuery.active == 0")));
        }
    }

    private static String getBy(WebElement webElement) {
        try {
            if (webElement instanceof RemoteWebElement) {
                try {
                    Field foundBy = webElement.getClass().getDeclaredField("foundBy");
                    foundBy.setAccessible(true);
                    return (String) foundBy.get(webElement);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            } else {
                LocatingElementHandler handler = (LocatingElementHandler) Proxy.getInvocationHandler(webElement);

                Field locatorField = handler.getClass().getDeclaredField("locator");
                locatorField.setAccessible(true);

                DefaultElementLocator locator = (DefaultElementLocator) locatorField.get(handler);

                Field byField = locator.getClass().getDeclaredField("by");
                byField.setAccessible(true);

                return byField.get(locator).toString();
            }
        } catch (IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }

        return null;
    }
}

Để kích hoạt khía cạnh này, tạo tập tin src\main\resources\META-INF\aop-ajc.xml và viết

<aspectj>
    <aspects>
        <aspect name="path.to.your.aspects.WebElementAspect"/>
    </aspects>
</aspectj>

Thêm cái này vào pom.xml

<properties>
    <aspectj.version>1.9.1</aspectj.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.0</version>
            <configuration>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
                </argLine>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>org.aspectj</groupId>
                    <artifactId>aspectjweaver</artifactId>
                    <version>${aspectj.version}</version>
                </dependency>
            </dependencies>
        </plugin>
</build>

Và đó là tất cả. Hy vọng nó giúp.


0

Bạn có thể giải quyết điều này bằng cách sử dụng chờ đợi rõ ràng để bạn không phải sử dụng chờ đợi khó khăn.

Nếu bạn tìm nạp tất cả các phần tử với một thuộc tính và lặp qua nó bằng cách sử dụng cho mỗi vòng lặp, bạn có thể sử dụng chờ trong vòng lặp như thế này,

List<WebElement> elements = driver.findElements("Object property");
for(WebElement element:elements)
{
    new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Object property"));
    element.click();//or any other action
}

hoặc cho một yếu tố duy nhất bạn có thể sử dụng mã bên dưới,

new WebDriverWait(driver,10).until(ExpectedConditions.presenceOfAllElementsLocatedBy("Your object property"));
driver.findElement("Your object property").click();//or anyother action 

-1

Trong Java 8, bạn có thể sử dụng phương thức rất đơn giản cho việc đó:

private Object retryUntilAttached(Supplier<Object> callable) {
    try {
        return callable.get();
    } catch (StaleElementReferenceException e) {
        log.warn("\tTrying once again");
        return retryUntilAttached(callable);
    }
}

-5
FirefoxDriver _driver = new FirefoxDriver();

// create webdriverwait
WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));

// create flag/checker
bool result = false;

// wait for the element.
IWebElement elem = wait.Until(x => x.FindElement(By.Id("Element_ID")));

do
{
    try
    {
        // let the driver look for the element again.
        elem = _driver.FindElement(By.Id("Element_ID"));

        // do your actions.
        elem.SendKeys("text");

        // it will throw an exception if the element is not in the dom or not
        // found but if it didn't, our result will be changed to true.
        result = !result;
    }
    catch (Exception) { }
} while (result != true); // this will continue to look for the element until
                          // it ends throwing exception.

Tôi đã thêm nó ngay bây giờ sau khi tìm ra nó. xin lỗi vì định dạng này là lần đầu tiên tôi đăng bài Chỉ cần cố gắng để giúp đỡ. Nếu bạn thấy nó hữu ích, vui lòng chia sẻ nó cho người khác :)
Alvin Vera

Chào mừng bạn đến với stackoverflow! Luôn luôn tốt hơn để cung cấp một mô tả ngắn cho mã mẫu để cải thiện độ chính xác của bài đăng :)
Phần mềm Picrofo

Chạy mã ở trên, bạn có thể bị mắc kẹt trong vòng lặp mãi mãi, nếu ví dụ có lỗi máy chủ trên trang đó.
munch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.